1. 認識 Objects 類別
開始進入主題!如果你厭倦了手動檢查是否為 null,以及為每個欄位撰寫雜湊計算,工具類別 java.util.Objects 可以幫上忙。它的目的——讓物件處理更簡單、更精煉且更安全。
它真的是一把「瑞士刀」:這個類別能安全地比較物件是否相等(避免觸發 NullPointerException)、方便地計算雜湊碼、透過比較器進行比較,並檢查參數是否為 null。
Objects.equals:考量 null 的安全比較
若直接寫 a.equals(b),而 a 為 null,就會拋出 NullPointerException。手動檢查既冗長又容易出錯。Objects.equals(a, b) 會替你處理:
- 如果兩者皆為 null——回傳 true。
- 如果只有一個為 null——回傳 false。
- 如果兩者都不是 null——呼叫一般的 equals。
import java.util.Objects;
String a = null;
String b = "Java";
System.out.println(Objects.equals(a, b)); // false
String c = null;
System.out.println(Objects.equals(a, c)); // true
String d = "Java";
String e = "Java";
System.out.println(Objects.equals(d, e)); // true
為什麼這麼方便? 程式碼更短、更乾淨,並能避免偶發的 NPE。
2. Objects.hash 與 hashCode:簡潔地計算雜湊碼
在同時覆寫 hashCode 與 equals 時很容易出錯,尤其當欄位很多時。手寫的程式碼常常又冗長又脆弱:
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
return result;
}
方法 Objects.hash 能解決這個問題——簡潔、安全,且支援 null:
import java.util.Objects;
public class Person {
private String name;
private int age;
// ... 建構子、getter 等等。
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
重要說明:Objects.hash 使用可變參數並會建立陣列——在少數高載場景中,手寫的 hashCode 可能更快。對大多數應用來說差異可以忽略。
3. Objects.compare:把比較委派給比較器
有時需要透過事先準備好的 Comparator 來比較兩個物件。與其直接呼叫 comparator.compare(a, b),也可以使用:
int result = Objects.compare(a, b, comparator);
這個方法會:
- 當物件相等時回傳 0。
- 視 null 為小於任何非 null 的物件。
- 其餘情況則把邏輯委派給傳入的比較器。
import java.util.Comparator;
import java.util.Objects;
class Person {
private String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Main {
public static void main(String[] args) {
Person a = new Person("Anna");
Person b = new Person("Boris");
Comparator<Person> byName = Comparator.comparing(Person::getName);
System.out.println(Objects.compare(a, b, byName)); // <0,因為 "Anna" < "Boris"
System.out.println(Objects.compare(a, null, byName)); // >0,因為 a != null
System.out.println(Objects.compare(null, b, byName)); // <0,因為 null < b
System.out.println(Objects.compare(null, null, byName)); // 0
}
}
4. Objects.requireNonNull:預防「隱形」錯誤
如果方法只能接受非 null 的值,請立即檢查。Objects.requireNonNull 會拋出帶有自訂訊息的 NullPointerException:
public void setName(String name) {
this.name = Objects.requireNonNull(name, "name 不可為 null");
}
5. 範例:使用 Objects 正確實作 equals、hashCode 與 compareTo
import java.util.Objects;
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = Objects.requireNonNull(name, "name 不可為 null");
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) {
if (this == o) return true; // 參考相等性比較
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
// 對 null 的安全比較
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age); // 簡潔且安全
}
@Override
public int compareTo(Person other) {
// 先按名稱比較,再按年齡
int cmp = name.compareTo(other.name);
if (cmp != 0) return cmp;
return Integer.compare(age, other.age);
}
}
現在可以把物件存放在 HashSet,把它們作為 HashMap 的鍵來使用,按相等性比較,並對清單進行排序(例如使用 Collections.sort)。
6. 真實場景中的應用:減少程式碼與錯誤
範例:應用程式中的使用者清單
有了正確成對的 equals/hashCode,在集合中的搜尋會更可預期:
import java.util.ArrayList;
import java.util.List;
List<Person> users = new ArrayList<>();
users.add(new Person("Anna", 25));
users.add(new Person("Boris", 30));
Person search = new Person("Anna", 25);
System.out.println(users.contains(search)); // true
範例:處理可為 null 的欄位
若類別有可能為 null 的欄位(例如中間名),請使用 Objects.equals 與 Objects.hash:
import java.util.Objects;
public class User {
private String firstName;
private String middleName; // 可能為 null
private String lastName;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(firstName, user.firstName)
&& Objects.equals(middleName, user.middleName)
&& Objects.equals(lastName, user.lastName);
}
@Override
public int hashCode() {
return Objects.hash(firstName, middleName, lastName);
}
}
7. 表格:Objects 類別的主要方法
| 方法 | 用途 | 使用範例 |
|---|---|---|
|
考量 null 的兩物件安全比較 | |
|
針對多個欄位簡潔地計算雜湊碼 | |
|
透過比較器比較,對 null 也安全 | |
|
檢查是否為 null,丟出 NullPointerException | |
|
檢查是否為 null/非 null(在 Stream API 中很方便) | |
8. 使用 Objects 類別方法的常見錯誤
錯誤 1:Objects.equals 未用於可能為 nullable 的欄位。 若直接以 equals 比較欄位,可能會遇到 NullPointerException。請使用 Objects.equals(middleName, other.middleName)。
錯誤 2:在 hashCode 中遺漏了部分欄位。 參與 equals 的欄位也必須參與 hashCode,否則 HashSet/HashMap 的行為將難以預期。
錯誤 3:手寫的 hashCode 有瑕疵。 正確處理係數與 null 檢查並不容易。Objects.hash 會幫你處理;若無嚴苛的效能需求,請優先使用它。
錯誤 4:在類別契約需要時,卻未使用 Objects.requireNonNull。 如果欄位不允許為 null,請在建構子/setter 中檢查——錯誤會立刻暴露,而不是「埋在」呼叫堆疊深處。
錯誤 5:對陣列使用 Objects.hash。 針對陣列應使用 Arrays.hashCode,而對巢狀陣列則使用 Arrays.deepHashCode;相似地,比較內容時請使用 Arrays.equals/Arrays.deepEquals。
GO TO FULL VERSION