继承(inheritance)是面向对象的重要概念。继承是除 组合(composition)之外,提高代码重复可用性(reusibility)的另一种重要方式。
类的继承
我们之前定义了 Human
类,现在我们定义一个 Woman
类,让它继承自 Human
类:
class Human
{
public int getHeight()
{
return this.height;
}
public void growHeight(int h)
{
this.height = this.height + h;
}
private int height;
}
// woman 类继承自 Human 类
class Woman extends Human
{
// woman 类新增的方法
public Human giveBirth()
{
System.out.println("Give birth");
return (new Human());
}
}
在 Java 中使用 extends
关键字来表示继承;现在 Woman
类既有了原先 Human
类所有 public
成员的方法,也有自身新增的 giveBirth()
方法。
这样,我们就省去了大量的输入。通过继承,我们创建了一个新类,叫做 衍生类 (derived class)。被继承的类(Human)称为 基类(base class)。衍生类以基类作为自己定义的基础,并补充基类中没有定义的 giveBirth()
方法。
可以用以下 Test 类测试:
public class Test
{
public static void main(String[] args)
{
Woman aWoman = new Woman();
aWoman.growHeight(120);
System.out.println(aWoman.getHeight());
}
}
输出:
120
衍生层
通过继承,我们创建了 Woman
类。整个过程可以分为三个层次:基类定义,衍生类定义,外部使用。
基类定义的层次就是正常的定义一个类,比如上面的Human类定义。
在外部使用者看来(比如 Test
类中创建 Woman
类对象),衍生类有一个统一的外部接口:
// Woman 对象拥有的外部接口
getHeight()
growHeight()
giveBirth()
对于外部使用者来说,上述接口就已经足够了。仅从接口看,衍生类也没有什么特别之处。
然而,当程序员在衍生类定义的层次时,就必须要小心:
首先,接口是混合的:getHeight()
方法和 growHeight()
方法来自基类,giveBirth()
方法则是在衍生类内部定义的。
还有更加复杂的地方。我们之前在类的内部可以自由访问类的成员(利用 this
指代对象)。然而,当我们在 Woman
类的定义范围内,我们无法访问基类 Human
的 private
成员(比如 height
)。
我们记得 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
成员。
super
和 this
类似,也是隐式参数。我们在类定义的不同层次时,this
会有不同的含义。要小心的使用 this
和 super
关键字。
Java 并不强制使用 this
和 super
。Java 在许多情况下可以自动识别成员的归属。但我觉得这是个好习惯。
protected
标为 protected
的成员在该类及其衍生类中可见。这个概念很容易理解,就是说,基类的 protected
成员可以被衍生层访问,但不能被外部访问,如下图:
方法覆盖
衍生类对象的外部接口最终由基类对象的 public
成员和衍生层的 public
成员共同构成。如果基类 public
成员和衍生层的 public
成员同名,Java 接口中呈现的究竟是哪一个呢?
我们在构造方法与方法重载中已经提到,Java 是同时通过方法名和参数列表来判断所要调用的方法的。方法是由方法名和参数列表共同决定的。上述问题中,如果只是方法名相同,而参数列表不同,那么两个方法会同时呈现到接口,不会给我们造成困扰。外部调用时,Java 会根据提供的参数,来决定使用哪个方法 。
如果方法名和参数列表都相同呢? 在衍生层时,我们还可以通过 super
和 this
来确定是哪一个方法。而在外部时,我们呈现的只是统一接口,所以无法同时提供两个方法。这种情况下,Java 会呈现衍生层的方法,而不是基层的方法。
这种机制叫做 方法覆盖(method overriding)。方法覆盖可以被很好的利用,用于修改基类成员的方法。比如,在衍生层,也就是定义 Woman
时,可以修改基类提供的 breath()
方法:
class Human
{
public int getHeight()
{
return this.height;
}
public void growHeight(int h)
{
this.height = this.height + h;
}
public void breath()
{
System.out.println("hu...hu...");
}
private int height;
}
// woman 类继承自 Human 类
class Woman extends Human
{
// woman 类新增的方法
public Human giveBirth()
{
System.out.println("Give birth");
return (new Human());
}
// 方法覆盖
public void breath()
{
System.out.println("su...");
}
}
public class Test
{
public static void main(String[] args)
{
Woman aWoman = new Woman();
aWoman.growHeight(120);
aWoman.breath();
}
}
输出结果:
su...
基类的 breath()
方法被 Woman
类的 breath()
方法覆盖了。
在这里我们也演示下 super
的用法:
// woman 类继承自 Human 类
class Woman extends Human
{
// woman 类新增的方法
public Human giveBirth()
{
System.out.println("Give birth");
return (new Human());
}
// 方法覆盖
public void breath()
{
super.breath(); // 调用基类 Human 的 breath() 方法
System.out.println("su...");
}
}
public class Test
{
public static void main(String[] args)
{
Woman aWoman = new Woman();
aWoman.growHeight(120);
aWoman.breath();
}
}
输出结果:
hu...hu...
su...
当我们位于衍生层,依然可以通过 super
来调用基类对象的 breath()
方法。
衍生类的构造方法
我们要在衍生类的定义中定义与类同名的构造方法。在该构造方法中:
由于在创建衍生对象的时候,基类对象先被创建和初始化,所以,基类的构造方法应该先被调用。我们可以使用
super(argument list)
的语句,来调用基类的构造方法。基类对象创建之后,开始构建衍生层(初始化衍生层成员)。这和一般的构建方法相同。
class Human
{
// 构造器
public Human(int h)
{
this.height = h;
}
public int getHeight()
{
return this.height;
}
public void growHeight(int h)
{
this.height = this.height + h;
}
public void breath()
{
System.out.println("hu...hu...");
}
private int height;
}
// woman 类继承自 Human 类
class Woman extends Human
{
// 构造器
public Woman(int h)
{
// 调用基类构造器
// 相当于执行 human(h)
super(h);
// 再执行衍生类新增的构造器方法
System.out.println("Hello, Pandora!");
}
// woman 类新增的方法
public Human giveBirth()
{
System.out.println("Give birth");
return (new Human(60));
}
// 方法覆盖
public void breath()
{
super.breath();
System.out.println("su...");
}
}
public class Test
{
public static void main(String[] args)
{
Woman aWoman = new Woman(100);
System.out.println(aWoman.getHeight());
}
}
输出结果:
Hello, Pandora!
100