什么是继承

继承是面向对象的三大特征之一,是实现代码复用的重要手段。

Java的继承通过extends关键字实现,实现继承的类称为子类,被继承的类称为父类(或者基类、父类)。

继承的语法如下:

1访问修饰符 class SubClass extends SuperClass{
2    //类定义部分
3}

子类继承了父类,将获得父类的全部成员变量和方法,但是不能获得父类的构造器

下面程序演示子类继承父类。

先定义一个父类:

1/**
 2 * Person类,作为父类
 3 */
 4public class Person {
 5
 6    String name;
 7
 8    int age;
 9
10    public void eat() {
11        System.out.println("吃饭");
12    }
13
14}

再定义一个子类,继承自上面已经定义好的父类:

1/**
2 * Man子类,继承Person父类
3 */
4public class Man extends Person {
5
6}

再定义一个测试类:

1public class Test {
 2
 3    public static void main(String[] args) {
 4        Man man = new Man();
 5
 6        // Man类本身并未定义name和age属性,但可以访问,说明Man继承了来自其父类的man和age属性
 7        man.name = "Java";
 8        man.age = 18;
 9
10        // eat方法也继承自其父类
11        man.eat();
12    }
13
14}

上面代码说明:当一个子类继承了一个父类,将获得父类的全部成员变量和方法。

做为子类,也可以有自己单独的成员变量和方法,其和父类构成了一般和特殊的关系。如,可以在上面的Man类添加自己的属性和方法:

1/**
 2 * Man子类,继承Person父类
 3 * 添加自己的属性和方法,这样一样,这个类的对象就拥有了父类的成员变量、方法和自己类中的成员变量、方法
 4 */
 5public class Man extends Person {
 6    boolean isRich;
 7
 8    public void earnMoney() {
 9        System.out.printf("男人要赚钱");
10    }
11}

Java的继承特点

Java语言中,每个类只有一个直接父类,是单继承的。例如下面代码编译会报错:

1class SubClass extends SuperClass1, SuperClass2,SuperClass3...
2

但是,可以有多个间接父类,如:

1public class Man extends Person{...}
2public class Person extends Creature{...}

上面代码中,CreaturePerson的直接父类,是Man的间接父类。

如果定义一个类时,没有显式地声明其直接父类,则这个类默认继承自java.lang,Object类。Object类是所有类的父类,要么是直接父类,要么是间接父类。

方法的重写

子类扩展了父类,是父类的一个特殊实现。当从父类继承来的方法不满足子类时,可以重写父类的方法。

下面程序先定义了一个Bird父类如下:

1/**
 2 * Person类,作为父类
 3 */
 4public class Person {
 5
 6    public void eat() {
 7        System.out.println("人吃饭");
 8    }
 9
10}

下面再定义一个Man类,这个类扩展了Person类,重写了Person类的eat()方法。

1/**
 2 * Man子类,继承Person父类
 3 */
 4public class Man extends Person {
 5
 6    // 子类重写了父类的eat()方法
 7    @Override
 8    public void eat() {
 9        System.out.println("男人要吃很多饭");
10    }
11
12}

再写一个测试类:

1public class Test {
2
3    public static void main(String[] args) {
4        Man man = new Man();
5        // 输出为:男人要吃很多饭
6        man.eat();
7    }
8
9}

可以看到,程序执行的是Man类中的eat()方法,而不是Person类的eat()方法。

像上面这种子类和父类包含有相同名称、相同参数列表的现象被称为方法的重写(Override),也被称为方法的覆盖。可以说子类重写了父类的方法,也可以说父类的方法被子类覆盖了。

方法的重写要遵循”两同两小一大“规则,即:

  1. ”两同“:方法名相同,参数列表相同
  2. “两小”:子类方法返回值类型应比父类方法返回值类型小或相等,子类方法声明抛出的异常应比父类小或相等
  3. ”一大“:子类方法的访问权限应比父类的大或相等。

super关键字

前文提到,子类可以重写父类的方法。当子类覆盖了父类方法后,子类对象将无法访问父类被重写的方法,但可以通过super关键字在子类对象的方法中访问父类被重写的方法。

代码是这样的:

1/**
 2 * Man子类,继承Person父类
 3 */
 4public class Man extends Person {
 5
 6    @Override
 7    public void eat() {
 8        // 可以在子类的方法中调用父类被覆盖的方法
 9        super.eat();
10        System.out.println("男人要吃很多饭");
11    }
12
13}

如果子类定义了和父类同名的实例变量,则在子类实例方法中将无法访问父类的该变量,这是因为被子类实例同名的实例变量隐藏了,这时候可以通过super关键字来访问。如下代码所示。

1class BaseClass{
 2    public int a = 5;
 3}
 4
 5public class SubClasss extends BaseClass{
 6    public int a = 7;
 7
 8    public void accessSubVar() {
 9        System.out.println(a);
10    }
11
12    public void accessBaseVar() {
13        System.out.print(super.a);
14    }
15
16    public static void main(String[] args) {
17        SubClasss s = new SubClasss();
18        s.accessSubVar();    // 输出7
19        s.accessBaseVar();    // 输出5
20    }
21
22}

当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承来的所有实例变量分配内存,即使子类定义了和父类同名的实例变量。也就是说,当系统创建一个Java对象时,如果该Java类有两个父类(一个直接父类A,一个间接父类B),假设A类中定义了2个实例变量,B类中定义了3个实例变量,自己子类中定义了2个实例变量,那么这个Java对象共有2+3+2个实例变量。

此外,super关键字还可以用来调用父类构造器。

1class Base {
 2    public double size;
 3    public String name;
 4    public Base(double size, String name) {
 5        this.size = size;
 6        this.name = name;
 7    }
 8}
 9
10public class Sub extends Base {
11    public String color;
12
13    public Sub(double size, String name, String color) {
14        // 使用super关键字显式调用父类构造器
15        super(size, name);
16        this.color = color;
17    }
18}

如果我们在子类构造器中未显式调用父类构造器,则会隐式调用父类无参数的构造器。

1class Person {
 2
 3    public Person() {
 4        System.out.println("父类构造器执行。。。");
 5    }
 6
 7}
 8
 9public class Man extends Person {
10
11    public Man() {
12        System.out.println("子类构造器执行。。。");
13    }
14
15    public static void main(String[] args) {
16        // 执行Man类的构造器时,会先执行其父类的构造器
17        // 所以输出结果为:父类构造器执行。。。子类构造器执行。。。
18        Man man = new Man();
19    }
20
21}

所以,调用子类构造器时,总是会先调用其父类的构造器,如果父类还继承自别的间接父类,则会依次往上追溯执行构造器。Object是所有类的父类,所以,构造任何Java对象时,总会先执行Object类的构造器。