CodeGym /Các khóa học /JAVA 25 SELF /Lớp Objects: các phương thức equals, hashCode, hash

Lớp Objects: các phương thức equals, hashCode, hash

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Làm quen với lớp Objects

Bắt đầu vào chủ đề! Nếu bạn đã mệt mỏi với việc tự kiểm tra giá trị có null hay không và viết công thức băm cho từng trường, lớp tiện ích java.util.Objects sẽ giúp bạn. Nhiệm vụ của nó — làm việc với đối tượng trở nên đơn giản, ngắn gọn và an toàn hơn.

Đây thực sự là “dao đa năng”: lớp này có thể so sánh bằng an toàn (không lo nhận NullPointerException), tính hash-code tiện lợi, so sánh thông qua Comparator và kiểm tra tham số có null hay không.

Objects.equals: so sánh an toàn có xét null

Nếu viết đơn giản a.equals(b)anull, bạn sẽ nhận NullPointerException. Tự kiểm tra thì cồng kềnh. Objects.equals(a, b) làm tất cả giúp bạn:

  • Nếu cả hai đều là null — trả về true.
  • Nếu chỉ một bên là null — trả về false.
  • Nếu cả hai không null — gọi equals thông thường.
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

Vì sao tiện? Mã ngắn gọn, sạch và tránh NPE vô tình.

2. Objects.hashhashCode: tính hash ngắn gọn

Khi override hashCode cùng với equals, rất dễ mắc lỗi, đặc biệt khi có nhiều trường. Mã viết tay thường cồng kềnh và dễ vỡ:

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    result = 31 * result + age;
    return result;
}

Phương thức Objects.hash giải quyết vấn đề — ngắn gọn, an toàn và xử lý null:

import java.util.Objects;

public class Person {
    private String name;
    private int age;

    // ... constructor, getters, v.v.

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Lưu ý quan trọng: Objects.hash dùng varargs và tạo mảng — ở vài điểm nóng hiệu năng hiếm gặp, tự viết hashCode có thể nhanh hơn. Với đa số ứng dụng, khác biệt là không đáng kể.

3. Objects.compare: ủy quyền so sánh cho Comparator

Đôi khi bạn cần so sánh hai đối tượng bằng một Comparator đã chuẩn bị sẵn. Thay vì gọi trực tiếp comparator.compare(a, b) bạn có thể dùng:

int result = Objects.compare(a, b, comparator);

Phương thức này:

  • Trả về 0 nếu các đối tượng bằng nhau.
  • Coi null “nhỏ hơn” bất kỳ đối tượng không-null nào.
  • Các trường hợp khác ủy quyền cho Comparator được truyền vào.
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, vì "Anna" < "Boris"
        System.out.println(Objects.compare(a, null, byName)); // >0, vì a != null
        System.out.println(Objects.compare(null, b, byName)); // <0, vì null < b
        System.out.println(Objects.compare(null, null, byName)); // 0
    }
}

4. Objects.requireNonNull: lá chắn trước lỗi “khó thấy”

Nếu một phương thức chỉ chấp nhận giá trị không-null, hãy kiểm tra ngay. Objects.requireNonNull sẽ ném NullPointerException kèm thông điệp của bạn:

public void setName(String name) {
    this.name = Objects.requireNonNull(name, "Tên không được là null");
}

5. Ví dụ: hiện thực đúng equals, hashCodecompareTo với Objects

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, "Tên không được là 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; // So sánh theo tham chiếu
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        // So sánh an toàn với null
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age); // Ngắn gọn và an toàn
    }

    @Override
    public int compareTo(Person other) {
        // So sánh theo tên trước, sau đó theo tuổi
        int cmp = name.compareTo(other.name);
        if (cmp != 0) return cmp;
        return Integer.compare(age, other.age);
    }
}

Giờ bạn có thể lưu trữ đối tượng trong HashSet, dùng chúng làm khóa trong HashMap, so sánh bằng và sắp xếp danh sách (ví dụ qua Collections.sort).

6. Ứng dụng thực tế: rút gọn mã và giảm lỗi

Ví dụ: danh sách người dùng trong ứng dụng

Nhờ cặp equals/hashCode đúng đắn, tra cứu trong collection hoạt động dễ dự đoán:

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

Ví dụ: làm việc với các trường có thể null

Nếu lớp có các trường có thể là null (ví dụ, tên đệm), hãy dùng Objects.equalsObjects.hash:

import java.util.Objects;

public class User {
    private String firstName;
    private String middleName; // Có thể là 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. Bảng: các phương thức chính của lớp Objects

Phương thức Mục đích Ví dụ sử dụng
Objects.equals(a, b)
So sánh an toàn hai đối tượng có xét null
Objects.equals(a, b)
Objects.hash(a, b, ...)
Tính hash-code ngắn gọn trên nhiều trường
Objects.hash(name, age)
Objects.compare(a, b, comparator)
So sánh qua Comparator, an toàn với null
Objects.compare(p1, p2, byNameComparator)
Objects.requireNonNull(obj[, msg])
Kiểm tra null, ném NullPointerException
Objects.requireNonNull(name, "Tên không được là null")
Objects.isNull(obj) / Objects.nonNull(obj)
Kiểm tra null/không-null (tiện trong Stream API)
list.stream().filter(Objects::nonNull)

8. Lỗi thường gặp khi dùng các phương thức của lớp Objects

Lỗi số 1: quên dùng Objects.equals cho các trường có thể null. Nếu so sánh trường trực tiếp bằng equals, bạn có thể gặp NullPointerException. Hãy dùng Objects.equals(middleName, other.middleName).

Lỗi số 2: không đưa hết các trường vào hashCode. Các trường tham gia equals cũng phải tham gia hashCode, nếu không hành vi của HashSet/HashMap sẽ trở nên khó lường.

Lỗi số 3: tự viết hashCode nhưng sai. Giữ hệ số và kiểm tra null không hề đơn giản. Objects.hash làm giúp bạn; hãy dùng nếu không có yêu cầu hiệu năng nghiêm ngặt.

Lỗi số 4: không dùng Objects.requireNonNull ở nơi đó thuộc về hợp đồng của lớp. Nếu một trường không cho phép null, hãy kiểm tra trong constructor/setter — lỗi sẽ lộ ra ngay, thay vì “lạc sâu” trong call stack.

Lỗi số 5: dùng Objects.hash cho mảng. Với mảng hãy dùng Arrays.hashCode, còn mảng lồng — Arrays.deepHashCode; tương tự để so sánh nội dung có Arrays.equals/Arrays.deepEquals.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION