
4.4 特殊变量super

扫码看视频
虽然面向对象的继承特性可以让我们很方便地重用代码,不过也存在一些问题,比如上一节中提到的子类对象的行为与父类对象的行为存在差异,又或者父类的方法在功能实现上存在不足之处,这些都可以通过方法的覆盖来解决。但在某些情况下,我们并不想整个替换父类的方法代码,只是希望在继承父类方法的同时,添加一些额外的功能。那这要怎么实现呢?如果子类覆盖了父类的对应方法,子类对象调用的就是重写的方法,那能不能在重写的方法中先调用父类的方法,再添加额外的功能实现代码呢?这就需要一个特殊的变量super登场了。
4.4.1 访问父类被子类覆盖的方法或隐藏的变量
特殊变量super提供了对父类(基类、派生类)的访问,如代码4.14所示。

在子类的breathe方法中,通过特殊变量super先调用父类被覆盖的breathe方法,然后是子类自己的代码。程序运行的结果是:

下面我们为Animal类添加两个数据成员:height和weight,分别代表动物的高度和重量。在Fish类中添加一个无参的构造方法,在构造方法中,可以直接访问继承的数据成员height和weight,并对其进行初始化,如代码4.15所示。

这段代码很简单,只是演示了除子类可以继承父类的方法以外,数据成员也可以继承。但这不是我们要说的重点,接下来为Fish类添加两个同名的数据成员:height和weight,如代码4.16所示。

这时,在子类Fish中拥有了与父类同名的两个成员变量height和weight,那么在Fish构造方法中访问的是子类的height和weight,还是父类的height和weight呢?显然,访问的是子类的height和weight。这种情况不叫覆盖,而是变量的隐藏。如果在子类中还想访问被隐藏的父类成员变量,那么一样可以通过super来访问,如代码4.17所示。

在Fish构造方法中,我们通过super访问Animal类被隐藏的成员变量height和weight。
程序运行的结果是:

代码4.17其实并无实际意义,在真实的项目开发中,几乎不会在子类中去定义与父类同名的成员变量,因为子类本身就可以直接或者间接地去使用父类中的数据成员。如果有这种情况出现,多半是类的设计出现了问题。我们给出这个例子,只是为了帮助读者区分变量的隐藏和方法的覆盖,以及介绍如何通过super变量来访问父类被子类隐藏的变量或覆盖的方法。
4.4.2 调用父类构造方法
下面我们先将Fish类的构造方法和成员变量定义代码都注释起来,然后为Animal类编写一个带两个参数的构造方法,方法的两个参数分别用于为数据成员height和weight赋初值,如代码4.18所示。

编译Animal.java,你会看到如图4-2所示的错误。

图4-2 父类定义了有参构造方法导致的错误
第4.1节我们已经介绍过,在子类对象构造时会先构造父类对象,而默认调用的是父类无参的构造方法,但现在父类Animal定义了一个有参的构造方法,Java编译器就不会再提供默认的构造方法。当你想要构造子类对象时,默认会去调用父类无参的构造方法,但这个构造方法并不存在,于是父类对象无法产生,自然子类对象也就无法构造了。
如何解决这个问题呢?自然也通过super来解决,实际上,每个子类构造方法的第一条语句都是隐含地调用super(),如果父类(基类、超类)没有这种形式的构造方法(即无参的构造方法),那么在编译的时候就会报错。
下面先为Fish类添加一个构造方法,然后通过super显式地调用父类的有参构造方法,如代码4.19所示。


在Fish构造方法中,通过super(50, 30)调用父类的有两个参数的构造方法,并向该方法传值。
再次编译运行,一切正常。
在真实场景中,更常见的是子类也提供带参数的构造方法(参数数量可以少于或等于父类构造方法),通过super调用传递默认值,或者直接以子类构造方法的参数去调用父类的构造方法,如代码4.20所示。
