CodeGym /課程 /JAVA 25 SELF /Objects 類別:equals、hashCode、hash 方法

Objects 類別:equals、hashCode、hash 方法

JAVA 25 SELF
等級 29 , 課堂 1
開放

1. 認識 Objects 類別

開始進入主題!如果你厭倦了手動檢查是否為 null,以及為每個欄位撰寫雜湊計算,工具類別 java.util.Objects 可以幫上忙。它的目的——讓物件處理更簡單、更精煉且更安全。

它真的是一把「瑞士刀」:這個類別能安全地比較物件是否相等(避免觸發 NullPointerException)、方便地計算雜湊碼、透過比較器進行比較,並檢查參數是否為 null

Objects.equals:考量 null 的安全比較

若直接寫 a.equals(b),而 anull,就會拋出 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.hashhashCode:簡潔地計算雜湊碼

在同時覆寫 hashCodeequals 時很容易出錯,尤其當欄位很多時。手寫的程式碼常常又冗長又脆弱:

@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 正確實作 equalshashCodecompareTo

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.equalsObjects.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 類別的主要方法

方法 用途 使用範例
Objects.equals(a, b)
考量 null 的兩物件安全比較
Objects.equals(a, b)
Objects.hash(a, b, ...)
針對多個欄位簡潔地計算雜湊碼
Objects.hash(name, age)
Objects.compare(a, b, comparator)
透過比較器比較,對 null 也安全
Objects.compare(p1, p2, byNameComparator)
Objects.requireNonNull(obj[, msg])
檢查是否為 null,丟出 NullPointerException
Objects.requireNonNull(name, "name 不可為 null")
Objects.isNull(obj) / Objects.nonNull(obj)
檢查是否為 null/非 null(在 Stream API 中很方便)
list.stream().filter(Objects::nonNull)

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

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION