每個 Java 範例都是獨立的 Main.java,可用 JDK 11 編譯執行;請一次複製一個範例。題庫使用 public class Solution 中的 public static solve(...) 方法,請保留題目指定的型別與參數,使用 return 交回答案,不必另外讀取輸入或撰寫 main。
01|類別是藍圖,物件是實例
類別把相關資料與操作放在一起:欄位記錄狀態,方法提供行為。new 建立新實例並執行建構子;變數保存該實例的參考。兩次 new 得到不同物件,實例欄位各自保存資料。
public class Main {
static class Counter {
int value;
void add(int amount) {
value += amount;
}
}
public static void main(String[] args) {
Counter first = new Counter();
Counter second = new Counter();
first.add(3);
System.out.println(first.value);
System.out.println(second.value);
}
}3 0
教材對照:PDF 第 2–8 頁
02|建構子與 this(...)
建構子名稱與類別相同,不寫回傳型別,連 void 也不能加。只在完全沒有自行宣告建構子時,編譯器才會提供預設無參數建構子。建構子可以超載;this(...) 呼叫同類別另一個建構子,在 Java 11 必須放在第一個敘述。
public class Main {
static class Student {
String name;
Student() {
this("新同學");
}
Student(String name) {
this.name = name;
}
}
public static void main(String[] args) {
Student first = new Student();
Student second = new Student("小芽");
System.out.println(first.name);
System.out.println(second.name);
}
}新同學 小芽
教材對照:PDF 第 9–12、33–37 頁
03|static 共用,實例各自保存
static 欄位由類別共用,適合保存全體共同資訊;實例欄位保存每個物件自己的狀態。static 方法沒有目前物件的 this,若要使用實例成員,需先取得明確的物件參考。呼叫靜態成員時以類別名稱表示最清楚。
public class Main {
static class Ticket {
static int created;
final int number;
Ticket() {
created++;
number = created;
}
}
public static void main(String[] args) {
Ticket first = new Ticket();
Ticket second = new Ticket();
System.out.println(first.number);
System.out.println(second.number);
System.out.println(Ticket.created);
}
}1 2 2
教材對照:PDF 第 3–4、13–14 頁
04|封裝:讓方法維護合法狀態
封裝是把資料與操作整合並限制直接修改。欄位設為 private,再提供能維護規則的方法。例如帳戶只接受正數存款,外部就不能任意把餘額設成負數。getter/setter 不是必備配對,應依操作需求設計。
public class Main {
static class Account {
private int balance;
boolean deposit(int amount) {
if (amount <= 0) {
return false;
}
balance += amount;
return true;
}
int getBalance() {
return balance;
}
}
public static void main(String[] args) {
Account account = new Account();
System.out.println(account.deposit(100));
System.out.println(account.deposit(-20));
System.out.println(account.getBalance());
}
}true false 100
教材對照:PDF 第 26 頁
05|繼承、super 與建構順序
extends 表示子類別是父類別的一種;一個類別只能直接繼承一個類別,但可實作多個介面。子類別不能直接讀取父類別 private 欄位。建構子不會被繼承;子類別透過 super(...) 呼叫父類別建構子,再完成自身初始化。
public class Main {
static class Person {
final String name;
Person(String name) {
this.name = name;
System.out.println("Person 初始化");
}
}
static class Student extends Person {
Student(String name) {
super(name);
System.out.println("Student 初始化");
}
}
public static void main(String[] args) {
Student student = new Student("小芽");
System.out.println(student.name);
}
}Person 初始化 Student 初始化 小芽
教材對照:PDF 第 27–37 頁
06|覆寫與動態多型
子類別以相同方法名稱與參數列提供新實作,稱為覆寫(override)。父類別型別的變數可以參考子類別實例;可呼叫哪些方法先由宣告型別決定,實例方法的實際實作再由執行時物件決定。加上 @Override,編譯器會幫你檢查是否真的覆寫。
- 覆寫不能縮小存取權限;參考型別回傳值可改成更具體的子型別,稱為共變回傳。
- 覆寫不能新增或擴大父方法未允許的 checked exception;可以少宣告或改成其子型別。
- final 方法不能被覆寫,private 方法不會被子類別繼承;static 方法屬於隱藏,不是實例方法覆寫。
public class Main {
static class Animal {
String move() { return "移動"; }
}
static class Cat extends Animal {
@Override
String move() { return "跑跳"; }
}
static class Bird extends Animal {
@Override
String move() { return "飛行"; }
}
static void showMove(Animal animal) {
System.out.println(animal.move());
}
public static void main(String[] args) {
Animal[] animals = {new Animal(), new Cat(), new Bird()};
for (Animal animal : animals) {
showMove(animal);
}
}
}移動 跑跳 飛行
教材對照:PDF 第 15–20、38–45、50–53 頁
07|欄位遮蔽與 instanceof 轉型
欄位不採用實例方法的動態派送:同名欄位取哪一份,依存取運算式的宣告型別決定。轉型只改變看待同一物件的型別,不會把父類別實例變成子類別。向下轉型前先用 instanceof 判斷;null instanceof 某型別 為 false。
public class Main {
static class Animal {
String label = "動物";
String sound() { return "聲音"; }
}
static class Cat extends Animal {
String label = "貓";
@Override
String sound() { return "喵"; }
}
public static void main(String[] args) {
Animal animal = new Cat();
System.out.println(animal.label);
System.out.println(animal.sound());
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
System.out.println(cat.label);
}
}
}動物 喵 貓
教材對照:PDF 第 21、44–49 頁
08|超載與 varargs
超載(overload)是相同方法名搭配不同參數型別、數量或順序;只改回傳型別不成立。呼叫哪個超載在編譯時根據參數型別選擇。varargs 用 int... values 接收不定數量參數,方法內當作陣列處理;每個方法最多一個,且要放最後。
public class Main {
static String describe(int value) {
return "數字=" + value;
}
static String describe(String value) {
return "文字=" + value;
}
static int sum(int... values) {
int total = 0;
for (int value : values) {
total += value;
}
return total;
}
public static void main(String[] args) {
System.out.println(describe(11));
System.out.println(describe("Java"));
System.out.println(sum(2, 4, 6));
System.out.println(sum());
}
}數字=11 文字=Java 12 0
教材對照:PDF 第 21–25 頁
09|is-a、has-a 與組合
is-a 表示『是一種』,例如 Computer 是 Machine;has-a 表示『擁有』,例如 Computer 有 Cpu。只有確實能當成父型別使用時才設計繼承;如果只是使用另一個物件的能力,將它保存為欄位通常較清楚。
public class Main {
static class Machine { }
static class Cpu {
int cores = 4;
}
static class Computer extends Machine {
final Cpu cpu = new Cpu();
}
public static void main(String[] args) {
Computer computer = new Computer();
System.out.println(computer instanceof Machine);
System.out.println(computer.cpu.cores);
}
}true 4
教材對照:PDF 第 54–55 頁
10|import static 簡化靜態成員名稱
一般 import 引入型別名稱;import static 引入該型別可存取的靜態成員名稱,例如 Math.max 與 System.out。它不會新增功能,也不會改變存取權限。名稱衝突時保留類別名稱,讓程式容易辨識。
import static java.lang.Math.max;
import static java.lang.System.out;
public class Main {
public static void main(String[] args) {
out.println(max(11, 7));
}
}11
教材對照:PDF 第 56–58 頁
11|抽象類別與介面
抽象類別用 abstract 宣告,可以保存欄位、提供已實作的方法,也可以留下沒有方法本體的抽象方法;不能直接 new 抽象類別。介面用 interface 定義可提供的能力,類別用 implements 實作。以下把共用的 amount 留在抽象父類別,再由 AddRule 完成 Rule 要求的 apply 方法。
- 介面中沒有方法本體的 apply 隱含 public abstract,實作方法也必須是 public。
- BaseRule 尚未實作 apply,因此必須維持 abstract;AddRule 完成實作後才能建立實例。
- Java 11 支援介面 default 方法,能提供預設實作。類別只能 extends 一個父類別,但可以 implements 多個介面。
public class Main {
interface Rule {
int apply(int value);
default String label() { return "規則"; }
}
static abstract class BaseRule implements Rule {
protected final int amount;
BaseRule(int amount) { this.amount = amount; }
}
static class AddRule extends BaseRule {
AddRule(int amount) { super(amount); }
@Override
public int apply(int value) { return value + amount; }
}
public static void main(String[] args) {
Rule rule = new AddRule(3);
System.out.println(rule.label());
System.out.println(rule.apply(4));
}
}規則 7
教材對照:PDF 第 33(Java 11 介面與抽象類別延伸) 頁
WHEN SOMETHING GOES WRONG
遇到錯誤,先檢查這裡
建構子前面誤寫 void
加上 void 後會成為一般方法,new 時不會把它當成建構子執行。檢查名稱與類別完全相同,且無回傳型別。
在 static 方法直接使用 this
static 沒有目前實例。先取得物件再呼叫實例方法,或重新思考操作是否應該是實例方法。
以為轉型後一定呼叫父類別方法
轉型不會改變實際物件;被覆寫的實例方法仍由物件決定。子類別實作內要明確呼叫父方法時用 super.method()。
PAUSE AND TRY
先不要急著看別人的寫法
- 建立兩個帳戶,只向第一個存款,第二個餘額應該是多少?
- 把多型範例的 Cat 加上新方法,Animal 變數為何不能直接呼叫?
- 比較 void run(int n) 與 void run(String text) 屬於超載還是覆寫。
先用紙筆預測,再改動範例中的數字或文字。結果與預期不同時,找出最早開始不同的那一步。
把這幾件事帶走
- 建構子建立合法起點,方法維護合法狀態。
- 宣告型別決定可呼叫內容,物件決定被覆寫實例方法的實作。
- 分清楚 is-a 繼承與 has-a 組合,避免讓關係過度複雜。
FROM READING TO PRACTICE
用三道免費題,練習本章觀念
先做簡單題確認語法,再把幾個步驟組合起來。三題都來自本章主題的 Java 免費題庫,難度表示需要組合的基本概念。
教材來源與 Java 11 對照
本章依據《第二部分 03 Java 物件導向.pdf》PDF 第 2–58 頁整理,將原有觀念重新編寫為可閱讀與執行的網站教學。頁碼為 PDF 檔案頁次;舊版語法說明與已知誤植依 Java 11 修正,補充內容另有標示。
需要查閱更完整的規則時,可參考以下官方文件。