1. 抽象類別的語法
先來弄清楚,什麼是 Java 的抽象類別,以及為什麼需要它。想像你有一組 LEGO:你有一堆零件(例如「輪子」、「積木」),也有某些特定模型才會用到的零件。抽象類別就像是一份操作零件的通用說明書,而不是某個具體模型的組裝步驟:積木如何卡榫、輪子如何裝在軸上、零件可以一層一層堆疊。抽象類別描述的是這些通用規則與必要步驟,但不細寫具體模型的組裝。具體的模型就是子類別:它們拿著通用說明,再補上缺少的步驟。
在 Java 中,抽象類別是一種無法直接被建立實例的類別(不能寫 new AbstractClass()),但可以被繼承,並在子類別中實作它「尚未完成」的方法。
抽象類別適用於以下情況:
- 一群物件擁有共同的行為或狀態,但部分邏輯各自不同。
- 你想先提供部分「預設」功能,剩下的留給子類別去實作。
- 你想禁止直接建立此類別的物件(例如,「動物」本身不會直接存在,但「貓」就會)。
如何宣告抽象類別
很簡單:在 class 前面加上關鍵字 abstract。
public abstract class Animal {
// 欄位(例如,動物的名字)
protected String name;
// 建構子
public Animal(String name) {
this.name = name;
}
// 抽象方法——只有宣告,沒有實作!
public abstract void makeSound();
// 一般(已有實作)的方法
public void sleep() {
System.out.println(name + " 睡覺:Zzz...");
}
}
特點:
- 抽象類別可以同時包含已有實作的方法與抽象方法。
- 抽象類別可以包含欄位、建構子,甚至 static 方法。
- 不能直接建立抽象類別的物件:
-
Animal a = new Animal("某個生物"); // 錯誤!
如何宣告抽象方法
抽象方法就是沒有方法本體的方法,也就是沒有大括號與內部程式碼。它用關鍵字 abstract 宣告,並且一定要以分號 ; 結尾。
public abstract void makeSound();
- 抽象方法只能在抽象類別內宣告。
- 繼承抽象類別的類別必須實作所有抽象方法,否則它本身也必須標記為 abstract。
2. 繼承抽象類別:運作方式
來看一個具體範例。假設有抽象類別 Animal 與抽象方法 makeSound()。現在建立子類別 Dog,並實作該方法:
public class Dog extends Animal {
public Dog(String name) {
super(name); // 呼叫基底類別的建構子
}
@Override
public void makeSound() {
System.out.println(name + " 在叫:汪汪!");
}
}
再看另一個子類別:
public class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println(name + " 喵叫:喵!");
}
}
現在我們可以在程式中使用這些類別:
public class Main {
public static void main(String[] args) {
Animal dog = new Dog("小球");
Animal cat = new Cat("穆爾卡");
dog.makeSound(); // 小球 在叫:汪汪!
cat.makeSound(); // 穆爾卡 喵叫:喵!
dog.sleep(); // 小球 睡覺:Zzz...
cat.sleep(); // 穆爾卡 睡覺:Zzz...
}
}
請注意:
我們可以宣告型別為 Animal 的變數,但真正建立的物件必須是具體(非抽象)的子類別。
示意圖:關係長什麼樣
. Animal (abstract)
/ \
Dog Cat
(實作 makeSound) (實作 makeSound)
3. 什麼時候用抽象類別,什麼時候用介面?
這是面試中最常見的問題之一!我們來理清一下:
- 抽象類別:當物件之間有共同狀態(例如欄位)、共同邏輯(有實作的方法),而你想提供一個可再擴充的「行為骨架」。
- 介面:當你只想定義一組方法(契約),不包含實作與狀態。自 Java 8 起,介面有了 default/static 方法,但它的本質仍是描述「物件該能做什麼」,而不是「怎麼做」。
生活範例:
「鳥」是抽象類別:所有鳥都有喙、翅膀,而且都會飛(但方式不同)。
「會飛的」是介面:不只鳥會飛,飛機、超級英雄也會!大家的飛法不同,但重點是「具備會飛的能力」。
4. 實用範例
範例 1:含部分實作的抽象類別
假設你在做一款遊戲,裡面有不同種類的交通工具。它們都可以移動,但方式各異。不過它們都有速度、名稱,以及一種標準的停止方式。
public abstract class Transport {
protected String name;
protected int speed;
public Transport(String name, int speed) {
this.name = name;
this.speed = speed;
}
// 抽象方法——由子類別實作
public abstract void move();
// 已實作的方法
public void stop() {
System.out.println(name + " 停下來了。");
}
}
public class Car extends Transport {
public Car(String name, int speed) {
super(name, speed);
}
@Override
public void move() {
System.out.println(name + " 以 " + speed + " 公里/小時 在道路上行駛。");
}
}
public class Bicycle extends Transport {
public Bicycle(String name, int speed) {
super(name, speed);
}
@Override
public void move() {
System.out.println(name + " 以 " + speed + " 公里/小時 踩踏板。");
}
}
使用方式:
Transport car = new Car("Toyota", 120);
Transport bike = new Bicycle("Stels", 25);
car.move(); // Toyota 以 120 公里/小時 在道路上行駛。
bike.move(); // Stels 以 25 公里/小時 踩踏板。
car.stop(); // Toyota 停下來了。
bike.stop(); // Stels 停下來了。
範例 2:帶參數的抽象方法
public abstract class Shape {
public abstract double area();
}
public class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public class Rectangle extends Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
使用方式:
Shape c = new Circle(3);
Shape r = new Rectangle(4, 5);
System.out.println("圓的面積:" + c.area());
System.out.println("長方形的面積:" + r.area());
5. 抽象類別的特性與注意事項
- 抽象類別可以使用任何存取修飾子:public、protected、private(例如用於欄位)。
- 可以宣告沒有任何抽象方法的抽象類別。如果你只是想禁止建立基底類別的實例,這會很有用。
- 若類別繼承了抽象類別卻沒有實作所有抽象方法,它本身也必須宣告為 abstract。
- 抽象類別可以有建構子。建立子類別物件時會呼叫它們(經由 super(...))。
- 抽象方法不能是 private(否則無法在子類別中實作)。
- 抽象類別可以包含 static 方法與欄位。
- 抽象類別可以實作介面,但不一定要在抽象類別中實作介面的方法——也可以交由子類別實作。
表格:抽象類別 vs 介面
| 抽象類別 | 介面 | |
|---|---|---|
| 關鍵字 | |
|
| 是否可包含欄位 | 是(任意) | 自 Java 8 起——僅 static/final |
| 是否可有已實作的方法 | 是 | 自 Java 8 起——default/static |
| 抽象方法 | 是 | 是 |
| 多重繼承 | 否 | 是(可實作多個介面) |
| 建構子 | 有 | 沒有 |
| 可否建立物件 | 否 | 否 |
6. 使用抽象類別與方法時的常見錯誤
錯誤 1:嘗試建立抽象類別的實例。
如果你寫了 new Animal("某個生物"),編譯器會立刻提醒抽象類別並非為此而設。記住:抽象就像「骨架」,不是「活生生的個體」。
錯誤 2:在子類別中漏掉某些抽象方法的實作。
如果你的類別少實作了基底抽象類別中的任一抽象方法,它本身就必須宣告為 abstract,否則會得到編譯錯誤。
錯誤 3:在非抽象類別中宣告抽象方法。
在 Java 中,不能在一般(非抽象)類別中宣告抽象方法——編譯器會立刻報錯。
錯誤 4:將抽象方法宣告為 private 或 static。
抽象方法不能是 private(不然無法被覆寫),也不能是 static(因為靜態方法不會被覆寫)。當然也不能是 final,因為 final 會禁止覆寫。
錯誤 5:忘了替抽象方法指定適當的存取修飾子。
如果你沒有明確指定修飾子,方法會是封包層級可見(package-private),有時這並非你想要的行為。
GO TO FULL VERSION