equals() và hashCode()
Thuộc phần Lập trình hướng đối tượng trong hành trình Java của Coddy. Bài học 42 trên 87.
Khi so sánh các đối tượng trong Java, toán tử == kiểm tra xem hai tham chiếu có trỏ đến cùng một vị trí bộ nhớ hay không. Để so sánh nội dung thực tế của các đối tượng, bạn cần ghi đè phương thức equals().
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
}Phương thức equals() tuân theo một mẫu chuẩn: trước tiên, kiểm tra xem đó có phải cùng một đối tượng không, sau đó xác minh kiểu dữ liệu, cuối cùng so sánh các trường liên quan.
Mỗi khi ghi đè equals(), bạn cũng phải ghi đè hashCode(). Đây là một quy tắc trong Java: các đối tượng bằng nhau phải có cùng mã băm. Các bộ sưu tập dựa trên mã băm như HashMap và HashSet dựa vào quy tắc này để hoạt động chính xác.
@Override
public int hashCode() {
return Objects.hash(name, age);
}Phương thức tiện ích Objects.hash() tạo mã băm từ cùng các trường được dùng trong equals(). Nếu hai đối tượng Person có cùng tên và tuổi, chúng sẽ được xem là bằng nhau và tạo ra mã băm giống hệt nhau, giúp đảm bảo hành vi nhất quán trong các tập hợp.
Thử thách
DễHãy xây dựng một hệ thống lưu hồ sơ sinh viên triển khai đúng cách việc so sánh hai đối tượng. Bạn sẽ tạo lớp Student, trong đó hai sinh viên được xem là bằng nhau nếu có cùng mã sinh viên và tên, bất kể chúng có phải là cùng một đối tượng trong bộ nhớ hay không.
Bạn sẽ sắp xếp code trong hai tệp:
Student.java: Tạo một lớp đại diện cho sinh viên với ba trường riêng tư:studentId(String),name(String) vàgpa(double). Thêm hàm khởi tạo để khởi tạo tất cả các trường và các phương thức getter cho từng trường.Ghi đè phương thức
equals()để hai đối tượng Student được xem là bằng nhau khi có cùngstudentIdVÀ cùngname. Làm theo mẫu chuẩn: kiểm tra xem có cùng tham chiếu hay không, kiểm tra giá trị null và kiểu, sau đó so sánh các trường liên quan.Ghi đè phương thức
hashCode()bằngObjects.hash()với cùng các trường được dùng trongequals(). Nhớ importjava.util.Objectsở đầu tệp.Main.java: Dùng lớp Student để so sánh các đối tượng sinh viên. Bạn sẽ nhận sáu đầu vào đại diện cho hai sinh viên: mã sinh viên, tên và GPA của sinh viên thứ nhất, tiếp theo là mã sinh viên, tên và GPA của sinh viên thứ hai.Tạo cả hai đối tượng Student, sau đó thực hiện ba phép so sánh và in kết quả:
- In
Same reference:, theo sau là kết quả cho biết sinh viên thứ nhất có bằng chính nó hay không (kết quả luôn phải là true) - In
Content equality:, theo sau là kết quả cho biết sinh viên thứ nhất có bằng sinh viên thứ hai hay không (dựa trên cách bạn triển khai equals) - In
Hash codes match:, theo sau là kết quả cho biết hai sinh viên có cùng mã băm hay không
- In
Bạn sẽ nhận sáu đầu vào: mã sinh viên thứ nhất (String), tên sinh viên thứ nhất (String), GPA của sinh viên thứ nhất (double), mã sinh viên thứ hai (String), tên sinh viên thứ hai (String) và GPA của sinh viên thứ hai (double).
Hãy chú ý rằng khi hai sinh viên có mã và tên trùng khớp, cả equals() đều trả về true VÀ mã băm của họ cũng trùng khớp. Đây là quy tắc bạn phải duy trì!
Tự làm thử
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Đọc dữ liệu của sinh viên thứ nhất
String id1 = scanner.nextLine();
String name1 = scanner.nextLine();
double gpa1 = scanner.nextDouble();
scanner.nextLine(); // đọc bỏ ký tự xuống dòng
// Đọc dữ liệu của sinh viên thứ hai
String id2 = scanner.nextLine();
String name2 = scanner.nextLine();
double gpa2 = scanner.nextDouble();
// TODO: Tạo hai đối tượng Student từ dữ liệu đầu vào
// TODO: In ra "Same reference: ", sau đó in kết quả so sánh student1 với chính nó
// TODO: In ra "Content equality: ", sau đó in kết quả so sánh student1 với student2
// TODO: In ra "Hash codes match: ", sau đó in kết quả kiểm tra hai sinh viên có cùng mã băm hay không
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Kiến thức cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcTrường (thuộc tính)Phương thức khởi tạoNạp chồng phương thức khởi tạoÔn tập: Máy tính đơn giản4Kế thừa
Kế thừa cơ bản (extends)Từ khóa superGhi đè phương thức (@Override)Chuỗi gọi hàm khởi tạoLớp ObjectKế thừa đơn và đa cấpVì sao không hỗ trợ kế thừa nhiều lớpÔn tập: Phân cấp lớp Employee7Phương thức đặc biệt và lớp Object
Phương thức toString()equals() và hashCode()Phương thức clone()compareTo() và ComparableGiao diện ComparatorÔn tập: sắp xếp tùy chỉnh2Bộ điều chỉnh quyền truy cập và đóng gói
Tổng quan về các mức truy cậpPhương thức getter và setterẨn thông tinTừ khóa finalÔn tập: Trình quản lý tài khoản ngân hàng5Tính đa hình
Kiến thức cơ bản về nạp chồng phương thứcGhi đè phương thức (thời gian chạy)Chuyển kiểu lên và chuyển kiểu xuốngToán tử instanceofLớp và phương thức trừu tượngÔn tập: máy tính hình học8Khái niệm OOP nâng cao
Kết hợp và kế thừaKết tập và hợp thànhLớp bên trong, lớp lồng nhau và lớp ẩn danhKiểu enum và phương thức enumBản ghi (Java 16+)Lớp sealed (Java 17+)3Thuộc tính lớp và thành viên tĩnh
Biến của instance và biến tĩnhPhương thức tĩnhKhối tĩnhHằng số (static final)Ôn tập: bộ đếm và tiện ích6Giao diện và lớp trừu tượng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnPhương thức mặc định và tĩnh trong giao diệnLớp trừu tượng và giao diệnGiao diện hàmÔn tập: hệ thống thanh toán9Kiểu tổng quát
Giới thiệu về kiểu tổng quátLớp tổng quátPhương thức tổng quátTham số kiểu có giới hạnKý tự đại diện (?, extends, super)Ôn tập: vùng chứa tổng quát12Mẫu thiết kế, phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMẫu IteratorTự luyện tập: Trình biên dịch Java online