理解 Java 的三大特性之继承性
回顾前面博文的例子(《理解Java的三大特性之封装性》):
public class Husband {
private String name;
private String sex;
private int age;
private Wife wife;
/*省略getter()、setter()*/
}
public class Wife {
private String name;
private int age;
private String sex;
private Husband husband;
/*省略getter()、setter()*/
}
在这个例子中,两个类有很多的共同的属性,这种重复代码是不能容忍的。所以此时就需要通过继承来实现了。
首页,我们离开编程世界,从现实出发,现实中存在的不仅仅只有夫妻关系,作为人类都有姓名性别等等一些基本属性,所以将这些人类公有的属性抽取出来无疑是很好的办法。让不同的子类通过继承去实现这些细节。
public class Person {
protected String name;
protected int age;
protected String sex;
}
public class Husband extends Person {
private Wife wife;
public void setWife(Wife wife) {
this.wife = wife;
}
}
public class Wife extends Person {
private Husband husband;
public Husband getHusband() {
return husband;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
}
使用继承后,两个子类 Husband
和 Wife
的代码量明显减少了,除此以外还可以非常清晰的看出三个类的关系。
实际上,子类是父类的特殊化,它除了拥有父类的特性以外,还拥有自己独有的特性。同时,在继承关系中,子类完全可以替换父类,反之则不可以。在使用继承时需要记住三句话:
- 子类拥有父类非
private
的属性和方法 - 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
- 子类可以重写父类的方法
构造器
子类可以继承父类的属性和方法,除了那些 private
的以外,构造器是不能被继承的,只能被调用。我们可以通过 super
关键字来调用父类中的构造器。
对于子类而言,其构造器的正确初始化是非常重要的,然而当且仅当只有一个方法可以保证这点:在构造器中调用父类构造器来完成初始化,而父类构造器具有执行父类初始化所需要的能力。
public class Person {
protected String name;
protected int age;
protected String sex;
public Person(){
System.out.println("Person Constructor...");
}
}
public class Husband extends Person {
private Wife wife;
public void setWife(Wife wife) {
this.wife = wife;
}
public Husband(){
System.out.println("Husband Constructor...");
}
}
public static void main(String[] args){
Husband husband = new Husband();
}
//output:
//Person Constructor...
//Husband Construcotr...
由此可见,子类的构建过程是从父类向外扩散的,虽然我们没有显式的调用父类的构造器,但是编译器会默认的给子类调用父类的构造器。
对于继承而言,子类会默认调用父类的构造器,但是如果没有默认的父类构造器(空参),子类必须要显式的指定父类的构造器,并且必须在子类构造器中的第一行代码。
继承虽然带来了好处,但是也有缺陷:
- 父类改变,子类就需要改变
- 继承破坏了封装,对于父类而言,它的实现细节对其子类来说是透明的
- 继承是一种强耦合关系。
所以说当我们使用继承的时候,我们需要确信使用继承确实是有效可行的办法。那么到底要不要使用继承呢?《Think in java》中提供了解决办法:问一问自己是否需要从子类向父类进行向上转型。如果必须向上转型,则继承是必要的,但是如果不需要,则应当好好考虑自己是否需要继承。