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) và a là null, 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.hash và hashCode: 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, hashCode và compareTo 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.equals và Objects.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 |
|---|---|---|
|
So sánh an toàn hai đối tượng có xét null | |
|
Tính hash-code ngắn gọn trên nhiều trường | |
|
So sánh qua Comparator, an toàn với null | |
|
Kiểm tra null, ném NullPointerException | |
|
Kiểm tra null/không-null (tiện trong Stream API) | |
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.
GO TO FULL VERSION