类继承中父类的构造和子类的构造使用
在Java中,任何class
的构造方法,第一行语句必须是调用父类的构造方法。
如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();,
public class Main {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 12, 89);
}
}
class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age); // 调用父类的构造方法Person(String, int)
this.score = score;
}
}
如果父类没有默认的构造方法,子类就必须显式调用
super()
并给出参数以便让编译器定位到父类的一个合适的构造方法
向上转型
如果一个引用变量的类型是Student
,那么它可以指向一个Student
类型的实例:
Student s = new Student();
如果一个引用类型的变量是Person
,那么它可以指向一个Person
类型的实例:
Person p = new Person();
现在问题来了:如果Student
是从Person
继承下来的,那么,一个引用类型为Person
的变量,能否指向Student
类型的实例?
Person p = new Student(); // ???
测试一下就可以发现,这种指向是允许的!
因为
Student
继承自Person
,因此,它拥有Person
的全部功能。
Person
类型的变量,如果指向Student
类型的实例,对它进行操作,是没有问题的!
这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。
向上转型实际上是把一个子类型安全地变为更加抽象的父类型:
Student s = new Student();
Person p = s; // upcasting, ok
Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok
注意到继承树是Student > Person > Object
,所以,可以把Student
类型转型为Person
,或者更高层次的Object
。
向下转型
和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。例如:
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!
如果测试上面的代码,可以发现:
Person
类型p1
实际指向Student
实例,Person
类型变量p2
实际指向Person
实例。在向下转型的时候,把p1
转型为Student
会成功,因为p1
确实指向Student
实例,把p2
转型为Student
会失败,因为p2
的实际类型是Person
,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
因此,向下转型很可能会失败。失败的时候,Java虚拟机会报ClassCastException
。
为了避免向下转型出错,Java提供了instanceof
操作符,可以先判断一个实例究竟是不是某种类型:
Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false
Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true
Student n = null;
System.out.println(n instanceof Student); // false
instanceof
实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为
null
,那么对任何instanceof
的判断都为false
。
区分继承和组合
在使用继承时,我们要注意逻辑一致性。
考察下面的Book
类:
class Book {
protected String name;
public String getName() {...}
public void setName(String name) {...}
}
这个Book
类也有name
字段,那么,我们能不能让Student
继承自Book
呢?
class Student extends Book {
protected int score;
}
显然,从逻辑上讲,这是不合理的,Student
不应该从Book
继承,而应该从Person
继承。
究其原因,是因为Student
是Person
的一种,它们是is关系,而Student
并不是Book
。实际上Student
和Book
的关系是has关系。
具有has关系不应该使用继承,而是使用组合,即Student
可以持有一个Book
实例:
class Student extends Person {
protected Book book;
protected int score;
}
因此,继承是is关系,组合是has关系。