由於多態的存在,每個子類都可以覆寫父類的方法,例如:
class Person {
public void run() { … }
}
class Student extends Person {
@Override
public void run() { … }
}
class Teacher extends Person {
@Override
public void run() { … }
}
從Person
類派生的Student
和Teacher
都可以覆寫run()
方法。
如果父類Person
的run()
方法沒有實際意義,能否去掉方法的執行語句?
class Person {
public void run(); // Compile Error!
}
答案是不行,會導致編譯錯誤,因爲定義方法的時候,必須實現方法的語句。
能不能去掉父類的run()
方法?
答案還是不行,因爲去掉父類的run()
方法,就失去了多態的特性。例如,runTwice()
就無法編譯:
public void runTwice(Person p) {
p.run(); // Person沒有run()方法,會導致編譯錯誤
p.run();
}
如果父類的方法本身不需要實現任何功能,僅僅是爲了定義方法簽名,目的是讓子類去覆寫它,那麼,可以把父類的方法聲明爲抽象方法:
class Person {
public abstract void run();
}
把一個方法聲明爲abstract
,表示它是一個抽象方法,本身沒有實現任何方法語句。因爲這個抽象方法本身是無法執行的,所以,Person
類也無法被實例化。編譯器會告訴我們,無法編譯Person
類,因爲它包含抽象方法。
必須把Person
類本身也聲明爲abstract
,才能正確編譯它:
abstract class Person {
public abstract void run();
}
抽象類
如果一個class
定義了方法,但沒有具體執行代碼,這個方法就是抽象方法,抽象方法用abstract
修飾。
因爲無法執行抽象方法,因此這個類也必須申明爲抽象類(abstract class)。
使用abstract
修飾的類就是抽象類。我們無法實例化一個抽象類:
Person p = new Person(); // 編譯錯誤
無法實例化的抽象類有什麼用?
因爲抽象類本身被設計成只能用於被繼承,因此,抽象類可以強迫子類實現其定義的抽象方法,否則編譯會報錯。因此,抽象方法實際上相當於定義了“規範”。
例如,Person
類定義了抽象方法run()
,那麼,在實現子類Student
的時候,就必須覆寫run()
方法:
public class Main {
public static void main(String[] args) {
Person p = new Student();
p.run();
}
}
abstract class Person {
public abstract void run();
}
class Student extends Person {
@Override
public void run() {
System.out.println("Student.run");
}
}
面向抽象編程
當我們定義了抽象類Person
,以及具體的Student
、Teacher
子類的時候,我們可以通過抽象類Person
類型去引用具體的子類的實例:
Person s = new Student();
Person t = new Teacher();
這種引用抽象類的好處在於,我們對其進行方法調用,並不關心Person
類型變量的具體子類型:
// 不關心Person變量的具體子類型:
s.run();
t.run();
同樣的代碼,如果引用的是一個新的子類,我們仍然不關心具體類型:
// 同樣不關心新的子類是如何實現run()方法的:
Person e = new Employee();
e.run();
這種儘量引用高層類型,避免引用實際子類型的方式,稱之爲面向抽象編程。
面向抽象編程的本質就是:
-
上層代碼只定義規範(例如:
abstract class Person
); -
不需要子類就可以實現業務邏輯(正常編譯);
-
具體的業務邏輯由不同的子類實現,調用者並不關心。