继承(inheritance)是面向对象的重要概念。继承是除组合(composition)之外,提高代码重复可用性(reusibility)的另一种重要方式。我们在组合(composition)中看到,组合是重复调用对象的功能接口。我们将看到,继承可以重复利用已有的类的定义。
类的继承
我们之前定义类的时候,都是从头开始,详细的定义该类的每一个成员。比如下面的Human类:
- class Human
- {
- /**
- * accessor
- */
- public int getHeight()
- {
- return this.height;
- }
- /**
- * mutator
- */
- public void growHeight(int h)
- {
- this.height = this.height + h;
- }
- /**
- * breath
- */
- public void breath()
- {
- System.out.println("hu...hu...");
- }
- private int height;
- }
现在要定义一个新的类,比如Woman类,并假设Woman与Human类相当类似:
Human & Woman
我们可以像以前一样,从头开始,完整的定义Woman类:
- class Woman{
- /**
- * accessor
- */
- public int getHeight()
- {
- return this.height;
- }
- /**
- * mutator
- */
- public void growHeight(int h)
- {
- this.height = this.height + h;
- }
- /**
- * breath
- */
- public void breath()
- {
- System.out.println("hu...hu...");
- }
- /**
- * new method
- */
- public Human giveBirth()
- {
- System.out.println("Give birth");
- return (new Human(20));
- }
- private int height;
- }
一个程序员在写上面程序的时候,会有很大的烦恼。许多定义都曾在Human类中写过,但我们还要重新敲一遍。Woman类只新增了一个giveBirth()方法 (该方法创建并返回一个新的Human对象)。
利用继承,我们可以避免上面的重复。让Woman类继承自Human类,Woman类就自动拥有了Human类中所有public成员的功能。
我们用extends关键字表示继承:
- class Woman extends Human
- {
- /**
- * new method
- */
- public Human giveBirth()
- {
- System.out.println("Give birth");
- return (new Human(20));
- }
- }
可以用以下Test类测试:
- public class Test
- {
- public static void main(String[] args)
- {
- Woman aWoman = new Woman();
- aWoman.growHeight(120);
- System.out.println(aWoman.getHeight());
- }
- }
通过继承,我们创建了Woman类。整个过程可以分为三个层次:基类定义,衍生类定义,外部使用。
基类定义的层次就是正常的定义一个类,比如上面的Human类定义。
在外部使用者看来(比如Test类中创建Woman类对象),衍生类有一个统一的外部接口:
对于外部使用者来说,上述接口就已经足够了。仅从接口看,衍生类也没有什么特别之处。
然而,当程序员在衍生类定义的层次时,就必须要小心:
首先,接口是混合的: getHeight()方法和growHeight()方法来自基类,giveBirth()方法则是在衍生类内部定义的。
还有更加复杂的地方。我们之前在类的内部可以自由访问类的成员(利用this指代对象)。然而,当我们在Woman类的定义范围内,我们无法访问基类Human的private成员。我们记得private的含义: private的成员仅供该类内部使用。Woman类是一个不同于Human类的新类,所以位于Human类的外部。在衍生类中,不能访问基类的private成员。
但有趣的是,我们的growHeight()和getHeight()方法依然可以运行。这说明基类的private成员存在,我们只是不能直接访问。
为了清晰概念,我们需要了解衍生类对象的生成机制。当我们创建一个衍生类的对象时,Java实际上先创建了一个基类对象(subobject),并在基类对象的外部(注意,这里是基类对象的外部,衍生类对象的内部),增加衍生类定义的其他成员,构成一个衍生类对象。外部使用者能看到的,就是基类和衍生类的public成员。如下图:
基类对象与衍生类对象
图中黄色为基类对象。基层的成员之间可以互相访问 (利用Human类定义中的this指代基类对象)。
蓝色部分为衍生对象新增的内容,我将这部分称为衍生层。蓝色和黄色部分共同构成衍生对象。衍生层的成员可以相互访问(Woman定义中的this)。更进一步,我们还可以访问基层中public的成员。为此,我们用super关键字来指代基类对象,使用super.member的方式来表示基层的(public)成员。
当我们位于衍生层时(也就是在定义Woman类时),不能访问红色的基层private成员。当我们位于外部时,既不能访问紫色的衍生层private成员,也不能访问红色的基层private成员。(衍生层的private成员有访问禁忌,所以标为斜线。基层的private成员访问禁忌最多,所以标为交叉斜线)
super和this类似,也是隐式参数。我们在类定义的不同层次时,this会有不同的含义。要小心的使用this和super关键字。
(Java并不强制使用this和super。Java在许多情况下可以自动识别成员的归属。但我觉得这是个好习惯。)
2、protected
我们之前介绍了两个访问权限相关的关键字,private和public,它们控制了成员的外部可见性。现在,我们介绍一个新的访问权限关键字: protected。
标为protected的成员在该类及其衍生类中可见。这个概念很容易理解,就是说,基类的protected成员可以被衍生层访问,但不能被外部访问,如下图:
3、方法覆盖
衍生类对象的外部接口最终由基类对象的public成员和衍生层的public成员共同构成。如果基类public成员和衍生层的public成员同名,Java接口中呈现的究竟是哪一个呢?
我们在构造方法与方法重载中已经提到,Java是同时通过方法名和参数列表来判断所要调用的方法的。方法是由方法名和参数列表共同决定的。上述问题中,如果只是方法名相同,而参数列表不同,那么两个方法会同时呈现到接口,不会给我们造成困扰。外部调用时,Java会根据提供的参数,来决定使用哪个方法(方法重载)。
如果方法名和参数列表都相同呢? 在衍生层时,我们还可以通过super和this来确定是哪一个方法。而在外部时,我们呈现的只是统一接口,所以无法同时提供两个方法。这种情况下,Java会呈现衍生层的方法,而不是基层的方法。
这种机制叫做方法覆盖(method overriding)。方法覆盖可以被很好的利用,用于修改基类成员的方法。比如,在衍生层,也就是定义Woman时,可以修改基类提供的breath()方法:
- class Woman extends Human
- {/**
- * new method
- */
- public Human giveBirth()
- {
- System.out.println("Give birth");
- return (new Human(20));
- }
- /**
- * override Human.breath()
- */
- public void breath()
- {
- super.breath();
- System.out.println("su...");
- }
- }
注意,此时我们位于衍生层,依然可以通过super来调用基类对象的breath()方法。当我们外部调用Woman类时,由于方法覆盖,就无法再调用基类对象的该方法了。
方法覆盖保持了基类对象的接口,而采用了衍生层的实现。使用时要注意一下三点:
1、三同原则:同方法名、同返回类型、同参数表
2、子类中的覆盖方法不能使用比父类中被覆盖的方法更严格的访问权限。
3、如需使用父类中原有的方法,可使用super关键字,该关键字引用了当前类父类的方法。
覆盖实际上是父子类之间的关系。
4、构造方法
在了解了基类对象和衍生层的概念之后,衍生类的构造方法也比较容易理解。
我们要在衍生类的定义中定义与类同名的构造方法。在该构造方法中:
1)由于在创建衍生对象的时候,基类对象先被创建和初始化,所以,基类的构造方法应该先被调用。我们可以使用super(argument list)的语句,来调用基类的构造方法。
2)基类对象创建之后,开始构建衍生层 (初始化衍生层成员)。这和一般的构建方法相同,参考构造方法与方法重载
比如下面的程序中,Human类有一个构造方法:
- class Human
- {
- /**
- * constructor
- */
- public Human(int h)
- {
- this.height = h;
- }
- /**
- * accessor
- */
- public int getHeight()
- {
- return this.height;
- }
- /**
- * mutator
- */
- public void growHeight(int h)
- {
- this.height = this.height + h;
- }
- /**
- * breath
- */
- public void breath()
- {
- System.out.println("hu...hu...");
- }
- private int height;
- }
- class Woman extends Human
- {
- /**
- * constructor
- */
- public Woman(int h)
- {
- super(h); // base class constructor
- System.out.println("Hello, Pandora!");
- }
- /**
- * new method
- */
- public Human giveBirth()
- {
- System.out.println("Give birth");
- return (new Human(20));
- }
- /**
- * override Human.breath()
- */
- public void breath()
- {
- super.breath();
- System.out.println("su...");
- }
- }
java的继承覆盖总结
1.构造函数:
当子类继承一个父类时,构造子类时需要调用父类的构造函数,存在三种情况
1)父类无构造函数或者一个无参数构造函数,子类若无构造函数或者有无参数构造函数,子类构造函数中不需要显式调用父类的构造函数,系统会自动在调用子类构造函数前调用父类的构造函数
2)父类只有有参数构造函数,子类在构造方法中必须要显示调用父类的构造函数,否则编译出错
3)父类既有无参数构造函数,也有有参构造函数,子类可以不在构造方法中调用父类的构造函数,这时使用的是父类的无参数构造函数 2.方法覆盖:
1)子类覆盖父类的方法,必须有同样的参数返回类型,否则编译不能通过
2)子类覆盖父类的方法,在jdk1.5后,参数返回类可以是父类方法返回类的子类
3)子类覆盖父类方法,可以修改方法作用域修饰符,但只能把方法的作用域放大,而不能把public修改为private
4)子类方法能够访问父类的protected作用域成员,不能够访问默认的作用域成员
5)子类的静态方法不能隐藏同名的父类实例方法
6)java与C++一样,继承的方法具有多态性
//以上6个结论已经过代码验证
3.成员覆盖:
1)当子类覆盖父类的成员变量时,父类方法使用的是父类的成员变量,子类方法使用的是子类的成员变量
这个听起来很容易理解的一回事,但是实际使用过程中很多人容易搞混:尤其是在多态的时候,调用一个被继承的方法,该方法访问是一个被覆盖的成员m,那么方法中到底是访问了父类的成员还是子类的成员m?结论是,若实际调用的是父类的方法,就使用了父类的该成员m,若实际调用的是子类的方法,就使用子类的成员m,记住一句,每个类使用成员都相当于在前面加了 一个this指针。
总结例子:
- class SuperClass {
- private int number;
- public SuperClass() {
- this.number = 0;
- }
- public SuperClass(int number) {
- this.number = number;
- }
- public int getNumber() {
- number++;
- return number;
- }
- }
- class SubClass1 extends SuperClass {
- public SubClass1(int number) {
- super(number);
- }
- }
- class SubClass2 extends SuperClass {
- private int number;
- public SubClass2(int number) {
- super(number);
- }
- }
- public class SubClass extends SuperClass {
- private int number;
- public SubClass(int number) {
- super(number);
- }
- public int getNumber() {
- number++;
- return number;
- }
- public static void main(String[] args) {
- SuperClass s = new SubClass(20);
- SuperClass s1 = new SubClass1(20);
- SuperClass s2 = new SubClass2(20);
- System.out.println(s.getNumber());
- System.out.println(s1.getNumber());
- System.out.println(s2.getNumber());
- //结论一:多态时,当子类覆盖了父类的方法,使用子类覆盖的方法
- //结论二:当子类覆盖父类的成员变量时,父类方法使用的是父类的成员变量,子类方法使用的是子类的成员变量
- }
- }
1
21
21