你好!在過去的課程中,我們遇到了接口並弄清楚了它們的用途。今天的話題與之前的話題相呼應。讓我們談談Java中的抽像類。
為什麼類被稱為“抽象”
您可能還記得“抽象”是什麼——我們已經講過了。:) 如果您忘記了,請不要害怕。請記住:OOP 的一個原則是,在設計類和創建對象時,我們應該只識別實體的主要屬性並丟棄次要屬性。例如,如果我們正在設計一個SchoolTeacher
類,我們幾乎不需要“高度”屬性。實際上,此屬性與教師無關。但是如果我們要創建一個BasketballPlayer
類,那麼增長將是一個重要的特徵。所以聽著。一個抽像類就像它們來的一樣抽象——對於一組未來的課程來說,這是一個未完成的“空白”。空白不能按原樣使用。太“生”了。但它描述了繼承抽像類的未來類將擁有的某些狀態和一般行為。
抽象 Java 類的示例
考慮一個簡單的汽車示例:
public abstract class Car {
private String model;
private String color;
private int maxSpeed;
public abstract void gas();
public abstract void brake();
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
}
這就是最簡單的抽像類的樣子。如您所見,它沒什麼特別的:) 為什麼我們需要它?首先,它以最抽象的方式描述了我們所需的實體,即汽車。我們使用抽像這個詞是有原因的。在現實世界中,沒有“抽象汽車”。有卡車、賽車、轎車、轎跑車和 SUV。 我們的抽像類只是一個“藍圖”,我們稍後將使用它來創建汽車類。
public class Sedan extends Car {
@Override
public void gas() {
System.out.println("The sedan is accelerating!");
}
@Override
public void brake() {
System.out.println("The sedan is slowing down!");
}
}
這與我們在繼承的課程中談到的非常相似。但是在那些課程中,我們有一個 Car 類,它的方法不是抽象的。但該解決方案有許多缺點,這些缺點已在抽像類中得到修復。首先,您不能創建抽像類的實例:
public class Main {
public static void main(String[] args) {
Car car = new Car(); // Error! The Car class is abstract!
}
}
Java 的創建者專門設計了這個“功能”。再次提醒:抽像類只是未來“普通”類的藍圖。你不需要藍圖的副本,對吧?而且您不會創建抽像類的實例 :) 但是如果類Car
不是抽象的,我們可以輕鬆地創建它的實例:
public class Car {
private String model;
private String color;
private int maxSpeed;
public void gas() {
// Some logic
}
public void brake() {
// Some logic
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(); // Everything is fine. A car is created.
}
}
現在我們的程序有某種難以理解的汽車——它不是卡車,不是賽車,也不是轎車,而且完全不清楚它是什麼。這是自然界中不存在的非常“抽象的汽車”。我們可以使用動物提供相同的示例。想像一下如果Animal
類(抽象動物)。目前還不清楚它是什麼動物,屬於什麼科,有什麼特徵。在您的程序中看到這一點會很奇怪。自然界中沒有“抽象動物”。只有狗、貓、狐狸、鼴鼠等。抽像類將我們從抽像對像中解放出來。它們為我們提供了基本的狀態和行為。例如,所有汽車都應該有model、color和maximum speed,你應該能夠應用油門和剎車。就是這樣。這是一個通用的抽象計劃。接下來你設計你需要的類。 注意:抽像類中的兩個方法也被指定為abstract,它們沒有任何實現。原因是一樣的:抽像類不會為抽象汽車創建默認行為。它們只是表明每輛車應該能夠做什麼。但是,如果確實需要默認行為,則可以在抽像類中實現方法。Java 不禁止這樣做:
public abstract class Car {
private String model;
private String color;
private int maxSpeed;
public void gas() {
System.out.println("Gas!");
}
public abstract void brake();
// Getters and setters
}
public class Sedan extends Car {
@Override
public void brake() {
System.out.println("The sedan is slowing down!");
}
}
public class Main {
public static void main(String[] args) {
Sedan sedan = new Sedan();
sedan.gas();
}
}
控制台輸出: “Gas!” 如您所見,我們在抽像類中實現了第一個方法,而不是第二個方法。因此,我們Sedan
類的行為分為兩部分:如果調用該gas()
方法,則調用'上升'到Car
抽象父類,但是我們覆蓋了類brake()
中的方法Sedan
。這樣非常方便和靈活。但是現在我們的類不那麼抽象了?畢竟它的方法實現了一半。這個實際上是一個非常重要的特性——如果至少有一個方法是抽象的,那麼這個類就是抽象的. 兩種方法中的一種,或至少一千種方法中的一種——都沒有區別。我們甚至可以實現所有的方法,並且不保留任何抽象方法。那麼它就是一個沒有抽象方法的抽像類。原則上,這是可能的,編譯器不會產生錯誤,但最好避免它:抽像這個詞失去了意義,你的程序員同行們會很驚訝:/ 同時,如果一個方法被標記使用抽像一詞,每個子類都必須實現它或將其聲明為抽象。否則,編譯器將產生錯誤。 當然,每個類只能繼承一個抽像類,所以抽像類和普通類在繼承上沒有區別。不管我們繼承抽像類還是普通類,都只能有一個父類。
為什麼Java沒有類的多重繼承
我們已經說過 Java 沒有多重繼承,但我們還沒有真正探究為什麼。讓我們現在嘗試這樣做。事實上,如果 Java 具有多重繼承,子類將無法決定它們應該選擇哪種特定行為。假設我們有兩個類——Toaster
和NuclearBomb
:
public class Toaster {
public void on() {
System.out.println("The toaster is on. Toast is being prepared!");
}
public void off() {
System.out.println("The toaster is off!");
}
}
public class NuclearBomb {
public void on() {
System.out.println("Boom!");
}
}
如您所見,兩者都有一個on()
方法。對於烤麵包機,它開始烘烤。對於核彈來說,它會引發爆炸。哎呀:/現在想像一下你決定(不要問我為什麼!)在兩者之間創造一些東西。這樣你就有了一MysteriousDevice
堂課! 當然,此代碼不起作用,我們僅將其作為示例提供,“但它可能是”:
public class MysteriousDevice extends Toaster, NuclearBomb {
public static void main(String[] args) {
MysteriousDevice mysteriousDevice = new MysteriousDevice();
mysteriousDevice.on(); // So what should happen here? Do we get toast or a nuclear apocalypse?
}
}
讓我們來看看我們有什麼。這個神秘裝置同時繼承了 Toaster 和 NuclearBomb。兩者都有on()
方法。因此,如果我們調用該on()
方法,則不清楚應該在對MysteriousDevice
像上調用哪個方法。對像不可能知道。最重要的是:NuclearBomb 沒有off()
方法,所以如果我們沒有猜對,就不可能禁用該設備。 正是由於這種“混亂”,對像不知道該表現出什麼行為,Java 的創建者才放棄了多重繼承。但是,您會記得 Java 類可以實現多個接口。 順便說一句,在您的學習中,您已經至少遇到過一個抽像類!
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>
這是你的老朋友,班級Calendar
。它是抽象的,有幾個孩子。其中之一是GregorianCalendar
。您已經在有關日期的課程中使用過它。:) 一切似乎都很清楚。只有一個問題:抽像類和接口之間的根本區別到底是什麼?為什麼他們將兩者都添加到 Java 而不是將語言限制為一種?畢竟,那已經完全足夠了。我們將在下一課討論這個!直到那時 :)
更多閱讀: |
---|
GO TO FULL VERSION