![Java无难事:详解Java编程核心思想与技术](https://wfqqreader-1252317822.image.myqcloud.com/cover/59/35011059/b_35011059.jpg)
4.4 特殊变量super
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_31.jpg?sign=1738910843-TCTTykSdfRfvABQduZNDNRGK2y6KAKBZ-0-3080984a54ea0784d39cd555bb01adb1)
扫码看视频
虽然面向对象的继承特性可以让我们很方便地重用代码,不过也存在一些问题,比如上一节中提到的子类对象的行为与父类对象的行为存在差异,又或者父类的方法在功能实现上存在不足之处,这些都可以通过方法的覆盖来解决。但在某些情况下,我们并不想整个替换父类的方法代码,只是希望在继承父类方法的同时,添加一些额外的功能。那这要怎么实现呢?如果子类覆盖了父类的对应方法,子类对象调用的就是重写的方法,那能不能在重写的方法中先调用父类的方法,再添加额外的功能实现代码呢?这就需要一个特殊的变量super登场了。
4.4.1 访问父类被子类覆盖的方法或隐藏的变量
特殊变量super提供了对父类(基类、派生类)的访问,如代码4.14所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_32.jpg?sign=1738910843-rvfyJfInJbs8h4UZ9f55Go8esDavJP96-0-a973251b94a950600e6e8895e11d1ffd)
在子类的breathe方法中,通过特殊变量super先调用父类被覆盖的breathe方法,然后是子类自己的代码。程序运行的结果是:
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_33.jpg?sign=1738910843-pjF4FZOR9YCVr1LgWpVlOl8gU9bOIDB1-0-64cbfc97663a04b1110ec467983f19ae)
下面我们为Animal类添加两个数据成员:height和weight,分别代表动物的高度和重量。在Fish类中添加一个无参的构造方法,在构造方法中,可以直接访问继承的数据成员height和weight,并对其进行初始化,如代码4.15所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_34.jpg?sign=1738910843-0KROIpCkVRka78qeoh1BjZOjJicDGDbu-0-67939db634a64aed7e967df2ca7fddc7)
这段代码很简单,只是演示了除子类可以继承父类的方法以外,数据成员也可以继承。但这不是我们要说的重点,接下来为Fish类添加两个同名的数据成员:height和weight,如代码4.16所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_35.jpg?sign=1738910843-CbBQH00QHFaSGuWd50AL1jXoAXZI4zir-0-09ca7300e7e99ae971144a7ac549380c)
这时,在子类Fish中拥有了与父类同名的两个成员变量height和weight,那么在Fish构造方法中访问的是子类的height和weight,还是父类的height和weight呢?显然,访问的是子类的height和weight。这种情况不叫覆盖,而是变量的隐藏。如果在子类中还想访问被隐藏的父类成员变量,那么一样可以通过super来访问,如代码4.17所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_36.jpg?sign=1738910843-wwZl5clH3ywfIWpUoAhkv5jcWdSA8Fuv-0-a548fed9f7e3f9bdf0afe4191722bd22)
在Fish构造方法中,我们通过super访问Animal类被隐藏的成员变量height和weight。
程序运行的结果是:
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_37.jpg?sign=1738910843-rIqh8ROeAw74KBhaMyPhIS3ZZs4us9xk-0-ccafb61c876a79769805bd9157e3e872)
代码4.17其实并无实际意义,在真实的项目开发中,几乎不会在子类中去定义与父类同名的成员变量,因为子类本身就可以直接或者间接地去使用父类中的数据成员。如果有这种情况出现,多半是类的设计出现了问题。我们给出这个例子,只是为了帮助读者区分变量的隐藏和方法的覆盖,以及介绍如何通过super变量来访问父类被子类隐藏的变量或覆盖的方法。
4.4.2 调用父类构造方法
下面我们先将Fish类的构造方法和成员变量定义代码都注释起来,然后为Animal类编写一个带两个参数的构造方法,方法的两个参数分别用于为数据成员height和weight赋初值,如代码4.18所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_38.jpg?sign=1738910843-VY61oTYQMw17U0MdOWb8zyh37SNu9qVK-0-d0b407a6f72ba9b1554a735f9b0fcb55)
编译Animal.java,你会看到如图4-2所示的错误。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_39.jpg?sign=1738910843-38Aw6M8hj3lAYcrfPyY4nCftwxoT1QxE-0-e81f3d75768f7eba5288a62c676049f3)
图4-2 父类定义了有参构造方法导致的错误
第4.1节我们已经介绍过,在子类对象构造时会先构造父类对象,而默认调用的是父类无参的构造方法,但现在父类Animal定义了一个有参的构造方法,Java编译器就不会再提供默认的构造方法。当你想要构造子类对象时,默认会去调用父类无参的构造方法,但这个构造方法并不存在,于是父类对象无法产生,自然子类对象也就无法构造了。
如何解决这个问题呢?自然也通过super来解决,实际上,每个子类构造方法的第一条语句都是隐含地调用super(),如果父类(基类、超类)没有这种形式的构造方法(即无参的构造方法),那么在编译的时候就会报错。
下面先为Fish类添加一个构造方法,然后通过super显式地调用父类的有参构造方法,如代码4.19所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_40.jpg?sign=1738910843-vNmdnAdx2D8OmlLjmg8rdozJ0WVsfVXY-0-ef82a4eef5158ec52460fedf98ac57d5)
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_41.jpg?sign=1738910843-wcgsLMwOyfhZVjQU3aghFRgwOZ0yh42z-0-9075e67e93d54eed8b7475b451354ece)
在Fish构造方法中,通过super(50, 30)调用父类的有两个参数的构造方法,并向该方法传值。
再次编译运行,一切正常。
在真实场景中,更常见的是子类也提供带参数的构造方法(参数数量可以少于或等于父类构造方法),通过super调用传递默认值,或者直接以子类构造方法的参数去调用父类的构造方法,如代码4.20所示。
![img](https://epubservercos.yuewen.com/AD1899/18685354708165706/epubprivate/OEBPS/Images/txt004_42.jpg?sign=1738910843-rzblpHxNkzah8pgRgji8aCfxGJ07vhst-0-278a869c8a326b9b8208e1a0532e0cad)