skip to content
BlogZzz

[Learn] Java 基礎:class、method 與 constructor

/ 12 min read

Updated:
Table of Contents

看 Java 程式碼時,一個 class 裡常會同時出現 public、private、static、final、constructor 與各種 method

這篇會用一個學生資料的範例,練習看懂以下內容

  • class 如何描述一種物件
  • constructor 如何建立物件
  • method 如何定義物件的行為
  • static 如何表示所有物件共用的成員
  • final 如何限制再次賦值、override 或繼承
  • package 如何組織程式碼
  • access modifier 如何限制可以存取成員的範圍

先建立整體概念

可以先用下面這條主線理解 Java class

class 定義一種物件
↓
field 保存物件的狀態
↓
constructor 建立物件並設定初始狀態
↓
method 讀取或改變物件的狀態

例如學生會有學號、姓名與分數,這些資料適合放在 field

建立學生時必須提供基本資料,所以交給 constructor

學生可以增加分數、判斷是否及格,這些行為則寫成 method

建立 package 與檔案

先建立以下目錄結構

src/
└── com/
└── example/
├── app/
│ └── Main.java
└── school/
└── Student.java

Java 通常會讓 package 名稱對應目錄路徑

  • com.example.school 對應 com/example/school
  • com.example.app 對應 com/example/app
  • package 名稱通常全部使用小寫
  • public class Student 通常放在 Student.java

先在 Student.java 寫入以下內容

package com.example.school;
public class Student {
public static final int PASSING_SCORE = 60;
private static int studentCount = 0;
private final String id;
private String name;
private int score;
public Student(String id, String name) {
this(id, name, 0);
}
public Student(String id, String name, int score) {
if (score < 0 || score > 100) {
throw new IllegalArgumentException("score 必須介於 0 到 100");
}
this.id = id;
this.name = name;
this.score = score;
studentCount++;
}
public void addScore(int points) {
if (points < 0) {
throw new IllegalArgumentException("points 不可小於 0");
}
score = Math.min(100, score + points);
}
public boolean hasPassed() {
return score >= PASSING_SCORE;
}
public String getSummary() {
return id + " / " + name + " / " + score + " / 通過=" + hasPassed();
}
public static int getStudentCount() {
return studentCount;
}
}

接著在另一個 package 的 Main.java 使用 Student

package com.example.app;
import com.example.school.Student;
public class Main {
public static void main(String[] args) {
Student amy = new Student("S001", "Amy", 55);
Student ben = new Student("S002", "Ben");
amy.addScore(15);
System.out.println(amy.getSummary());
System.out.println(ben.getSummary());
System.out.println("目前學生數:" + Student.getStudentCount());
System.out.println("及格分數:" + Student.PASSING_SCORE);
}
}

在專案根目錄編譯並執行

Terminal window
javac -d out src/com/example/school/Student.java src/com/example/app/Main.java
java -cp out com.example.app.Main

執行結果如下

S001 / Amy / 70 / 通過=true
S002 / Ben / 0 / 通過=false
目前學生數:2
及格分數:60

class 是物件的定義

class 用來定義資料與行為

public class Student {
private String name;
private int score;
}

這段程式只定義了 Student 這種型別,還沒有建立真正的學生物件

使用 new 才會建立 instance

Student amy = new Student("S001", "Amy", 55);
Student ben = new Student("S002", "Ben");

amy 與 ben 都是 Student instance,但各自保存自己的 name 與 score

在 class 內宣告的變數稱為 field,也常稱為 member variable

private final String id;
private String name;
private int score;

field 描述物件需要保存的狀態

constructor 負責建立物件

constructor 的名稱必須與 class 相同,而且不能寫回傳型別,連 void 也不能寫

public Student(String id, String name, int score) {
this.id = id;
this.name = name;
this.score = score;
}

執行 new Student(...) 時,Java 會呼叫參數相符的 constructor

this.id 代表目前物件的 field,右邊的 id 則是傳入 constructor 的 parameter

同一個 class 可以有多個參數不同的 constructor,這稱為 constructor overloading

public Student(String id, String name) {
this(id, name, 0);
}

this(id, name, 0) 會呼叫另一個 constructor,並把預設分數設為 0

這種寫法可以集中初始化與驗證邏輯,避免兩個 constructor 各自維護一份相似程式碼

如果完全沒有宣告 constructor,compiler 會自動提供一個無參數 constructor

但只要自己宣告了任何 constructor,compiler 就不會再自動補上無參數版本

method 定義物件可以做什麼

method 是一段有名稱、可以接收參數並可能回傳結果的行為

public boolean hasPassed() {
return score >= PASSING_SCORE;
}

可以把 method declaration 拆成以下部分

部分範例用途
access modifierpublic決定誰可以呼叫
回傳型別boolean說明回傳值的型別
method 名稱hasPassed描述這個行為
parameter()接收呼叫者提供的資料
method body{ ... }實際執行的程式碼

沒有回傳值時使用 void

public void addScore(int points) {
score = Math.min(100, score + points);
}

呼叫 instance method 時,要透過某個物件

amy.addScore(15);
boolean passed = amy.hasPassed();

如果 method 會改變 field,它改變的是接收這次呼叫的那個物件

amy.addScore(15) 不會改到 ben 的分數

static 屬於 class 本身

一般 field 與 method 屬於每一個 instance,static 成員則屬於 class 本身,由所有 instance 共用

private static int studentCount = 0;
public static int getStudentCount() {
return studentCount;
}

每次建立 Student 都會讓共用的 studentCount 加一

呼叫 static method 時,建議使用 class 名稱

int count = Student.getStudentCount();

static method 沒有特定的目前物件,因此不能直接使用 this,也不能直接存取 name、score 等 instance field

main 也是 static method,JVM 不需要先建立 Main 物件就能把它當成程式進入點

public static void main(String[] args) {
// 程式從這裡開始執行
}

final 代表不能再改變某種關係

final 放在不同位置時,限制的內容也不同

位置意義
variable 或 field初始化後不能再次賦值
methodsubclass 不能 override
class不能被其他 class 繼承

學號建立後不應改變,所以使用 final field

private final String id;

final field 可以在宣告時賦值,也可以在每條 constructor 路徑中完成賦值

所有學生共用且不應變動的及格分數,適合宣告為 constant

public static final int PASSING_SCORE = 60;

Java constant 通常使用 static final,名稱採全大寫並以底線分隔單字

需要禁止繼承時,可以把整個 class 宣告為 final

public final class ScoreCalculator {
}

需要注意的是,final reference 不能改成指向另一個物件,但它所指向的物件不一定不可變

final int[] scores = {60};
scores[0] = 100; // 可以修改陣列內容
// scores = new int[] {80}; // 無法再次指定 reference

因此 final 不等於 immutable

package 用來組織與區分 class

package 可以避免 class 名稱衝突,也能把用途相近的 class 放在同一個命名空間

package com.example.school;

package declaration 要放在原始碼開頭,並位於 import 與 class declaration 之前

另一個 package 要使用 Student 時,可以先 import

package com.example.app;
import com.example.school.Student;

也可以不用 import,直接寫完整名稱

com.example.school.Student amy =
new com.example.school.Student("S001", "Amy");

實務上通常使用 import,讓程式碼保持簡潔

package 不只是資料夾分類,也會影響沒有寫 access modifier 的 class 與 member 可以被誰使用

access modifier 控制可見範圍

Java 常見的四種存取層級如下

修飾方式同一個 class同一個 package不同 package 的 subclass其他 class
public可以可以可以可以
protected可以可以可以不可以
不寫 modifier可以可以不可以不可以
private可以不可以不可以不可以

不寫 modifier 時通常稱為 package-private,而不是 default

這裡的 private field 只能由 Student 自己管理

private String name;
private int score;

外部程式不能直接寫入分數,只能透過公開的 method

amy.addScore(15);
// amy.score = 200; // 無法編譯,score 是 private

這樣可以把分數範圍檢查集中在 Student 內部,避免物件進入不合理的狀態

protected 主要用在繼承設計

不同 package 的 subclass 可以透過繼承關係存取 protected member,但一般外部物件不行

另外,最外層的 class 只能宣告為 public 或省略 modifier

private 與 protected 可以用在 nested class,但不能直接用在最外層 class

如何選擇 access modifier

可以從最小必要範圍開始選擇

  • field 優先使用 private
  • 要提供給其他 package 使用的 API 才設為 public
  • 只在同一個 package 協作時可以省略 modifier
  • 確定要提供 subclass 擴充時才使用 protected

這不是要求所有 field 都要機械式地產生 getter 與 setter

如果分數只能增加而不能任意指定,提供 addScore 會比公開 setScore 更能表達規則

public void addScore(int points) {
if (points < 0) {
throw new IllegalArgumentException("points 不可小於 0");
}
score = Math.min(100, score + points);
}

class 的公開 method 應該描述允許外部執行的行為,而不只是暴露內部 field

結論

  • class 把狀態與行為組合成一種型別
  • constructor 在建立 instance 時設定有效的初始狀態
  • method 定義物件或 class 可以執行的行為
  • static member 屬於 class,instance member 屬於各自的物件
  • final 對 variable、method 與 class 有不同限制,不代表物件一定 immutable
  • package 同時負責命名空間、程式碼組織與部分存取邊界
  • access modifier 應從最小必要權限開始選擇,避免直接暴露內部狀態

先把這幾個概念連起來,就能讀懂多數 Java class 的基本結構,也能開始把資料與規則寫成自己的 class

參考資料