類繼承中父類的構造和子類的構造使用
在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關係。