"Bây giờ tôi sẽ cho bạn biết về một số phương pháp cũng hữu ích không kém:  equals(Object o) & hashCode() ."

"Có lẽ bạn đã nhớ rằng, trong Java, khi so sánh các biến tham chiếu, bản thân các đối tượng không được so sánh, mà là các tham chiếu đến các đối tượng."

Mã số Giải trình
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j);
i không bằng j
Các biến trỏ đến các đối tượng khác nhau.
Mặc dù các đối tượng chứa cùng một dữ liệu.
Integer i = new Integer(1);
Integer j = i;
System.out.println(i==j);
tôi bằng j. Các biến chứa một tham chiếu đến cùng một đối tượng.

"Vâng, tôi nhớ điều đó."

Bình  đẳng .

"Phương thức bằng là giải pháp tiêu chuẩn ở đây. Mục đích của phương thức bằng là để xác định xem các đối tượng có giống nhau bên trong hay không bằng cách so sánh những gì được lưu trữ bên trong chúng."

"Và làm thế nào nó làm điều đó?"

"Tất cả đều rất giống với phương thức toString()."

Lớp Object có cách triển khai phương thức bằng của riêng nó, phương thức này chỉ đơn giản là so sánh các tham chiếu:

public boolean equals(Object obj)
{
return (this == obj);
}

"Tuyệt... quay lại chuyện đó lần nữa nhé?"

"Hãy ngẩng cao đầu! Nó thực sự rất khó."

"Phương pháp này được tạo ra để cho phép các nhà phát triển ghi đè lên nó trong các lớp của riêng họ. Rốt cuộc, chỉ nhà phát triển của một lớp mới biết dữ liệu nào có liên quan và dữ liệu nào không khi so sánh."

"Bạn có thể cung cấp một ví dụ?"

"Chắc chắn rồi. Giả sử chúng ta có một lớp đại diện cho các phân số toán học. Nó sẽ trông như thế này:"

Ví dụ:
class Fraction
{
private int numerator;
private int denominator;
Fraction(int numerator, int denominator)
{
this.numerator  = numerator;
this.denominator = denominator;
}public boolean equals(Object obj)
{
if (obj==null)
return false;

if (obj.getClass() != this.getClass() )
return false;

Fraction other = (Fraction) obj;
return this.numerator* other.denominator == this.denominator * other.numerator;
}
}
Ví dụ gọi phương thức:
Fraction one = new Fraction(2,3);
Fraction two = new Fraction(4,6);
System.out.println(one.equals(two));
Cuộc gọi phương thức sẽ trả về true.
Phân số 2/3 bằng phân số 4/6

"Bây giờ, hãy mổ xẻ ví dụ này."

"Chúng tôi đã ghi đè phương thức bằng , vì vậy các đối tượng Phân số sẽ có cách triển khai riêng.

"Có một số kiểm tra trong phương pháp:"

" 1)  Nếu đối tượng được truyền để so sánh là null , thì các đối tượng đó không bằng nhau. Nếu bạn có thể gọi phương thức bằng trên một đối tượng, thì nó chắc chắn không phải là null ."

" 2)  So sánh lớp. Nếu các đối tượng là thể hiện của các lớp khác nhau, thì chúng tôi sẽ không thử so sánh chúng. Thay vào đó, chúng tôi sẽ ngay lập tức sử dụng return false để chỉ ra rằng đây là các đối tượng khác nhau."

" 3)  Mọi người đều nhớ từ lớp hai rằng 2/3 bằng 4/6. Nhưng làm thế nào để bạn kiểm tra điều đó?"

2/3 == 4/6
Chúng tôi nhân cả hai bên với cả hai ước số (6 và 3) và chúng tôi nhận được:
6 * 2 == 4 * 3
12 == 12
Nguyên tắc chung:
Nếu
a / b == c / d
Thì
a * d == c * b

"Theo đó, trong phần thứ ba của phương thức bằng , chúng tôi chuyển đối tượng đã truyền thành Phân số và so sánh các phân số."

"Rõ rồi. Nếu chỉ so sánh tử số với tử số, mẫu số với mẫu số thì 2/3 không bằng 4/6."

"Giờ tôi đã hiểu ý của bạn khi bạn nói rằng chỉ người phát triển lớp mới biết cách so sánh nó một cách chính xác."

"Vâng, nhưng đó mới chỉ là một nửa câu chuyện.  Có một phương pháp khác: hashCode(). "

"Mọi thứ về phương thức bằng đều có ý nghĩa bây giờ, nhưng tại sao chúng ta cần  hashCode ()? "

" Phương pháp hashCode là cần thiết để so sánh nhanh."

" Phương thức bằng có một nhược điểm lớn: nó hoạt động quá chậm. Giả sử bạn có một Tập hợp gồm hàng triệu phần tử và cần kiểm tra xem nó có chứa một đối tượng cụ thể hay không. Bạn làm điều đó như thế nào?"

"Tôi có thể duyệt qua tất cả các phần tử bằng cách sử dụng một vòng lặp và so sánh đối tượng với từng đối tượng trong tập hợp. Cho đến khi tôi tìm thấy đối sánh."

"Và nếu nó không có ở đó? Chúng tôi sẽ thực hiện hàng triệu phép so sánh chỉ để tìm ra rằng vật thể không có ở đó? Điều đó có vẻ rất nhiều phải không?"

"Vâng, ngay cả tôi cũng nhận ra rằng so sánh như vậy là quá nhiều. Còn cách nào khác không?"

"Có, bạn có thể sử dụng hashCode () cho việc này.

Phương thức hashCode () trả về một số cụ thể cho từng đối tượng. Nhà phát triển của một lớp quyết định số nào được trả về, giống như họ làm đối với phương thức bằng.

"Hãy xem một ví dụ:"

"Hãy tưởng tượng rằng bạn có một triệu số gồm 10 chữ số. Sau đó, bạn có thể đặt mã băm của mỗi số là phần còn lại sau khi chia số đó cho 100."

Đây là một ví dụ:

Con số mã băm của chúng tôi
1234567890 90
9876554321 21
9876554221 21
9886554121 21

"Vâng, điều đó có ý nghĩa. Và chúng ta sẽ làm gì với mã băm này?"

"Thay vì so sánh các con số, chúng tôi so sánh mã băm của chúng . Cách đó nhanh hơn."

"Và chúng tôi chỉ gọi bằng nếu mã băm của chúng bằng nhau."

"Vâng, điều đó nhanh hơn. Nhưng chúng tôi vẫn phải thực hiện hàng triệu phép so sánh. Chúng tôi chỉ đang so sánh các số nhỏ hơn và chúng tôi vẫn phải gọi bằng nhau cho bất kỳ số nào có Mã băm phù hợp."

"Không, bạn có thể thoát khỏi với số lượng so sánh nhỏ hơn nhiều."

"Hãy tưởng tượng rằng Bộ của chúng tôi lưu trữ các số được nhóm hoặc sắp xếp theo Mã băm (sắp xếp chúng theo cách này về cơ bản là nhóm chúng lại, vì các số có cùng Mã băm sẽ nằm cạnh nhau). Sau đó, bạn có thể loại bỏ các nhóm không liên quan rất nhanh chóng và dễ dàng. Thế là đủ để kiểm tra một lần cho mỗi nhóm để xem mã băm của nó có khớp với mã băm của đối tượng hay không."

"Hãy tưởng tượng bạn là một sinh viên đang tìm kiếm một người bạn mà bạn có thể nhận ra khi nhìn thấy và người mà chúng ta biết sống ở Ký túc xá 17. Sau đó, bạn chỉ cần đến từng ký túc xá của trường đại học và hỏi, 'Đây có phải là Ký túc xá 17 không?' Nếu không, thì bạn bỏ qua mọi người trong ký túc xá và chuyển sang phòng tiếp theo. Nếu câu trả lời là 'có', thì bạn bắt đầu đi qua từng phòng, tìm kiếm bạn của mình."

"Trong ví dụ này, số ký túc xá (17) là mã băm."

"Nhà phát triển triển khai hàm hashCode phải biết những điều sau:"

A)  hai đối tượng khác nhau có thể có cùng mã băm  (những người khác nhau có thể sống trong cùng một ký túc xá)

B)  các đối tượng giống nhau  ( theo phương thức bằngphải có cùng mã băm. .

C)  mã băm phải được chọn sao cho không có nhiều đối tượng khác nhau có cùng mã băm.  Nếu có, thì lợi thế tiềm năng của mã băm sẽ bị mất (Bạn đến Ký túc xá 17 và thấy rằng một nửa trường đại học sống ở đó. Đáng tiếc!).

"Và bây giờ là điều quan trọng nhất. Nếu bạn ghi đè phương thức bằng , bạn tuyệt đối phải ghi đè phương thức hashCode () và tuân thủ ba quy tắc được mô tả ở trên.

"Lý do là: trong Java, các đối tượng trong một bộ sưu tập luôn được so sánh/truy xuất bằng hashCode() trước khi chúng được so sánh/truy xuất bằng cách sử dụng bằng.  Và nếu các đối tượng giống hệt nhau có các mã băm khác nhau, thì các đối tượng sẽ được coi là khác nhau và phương thức bằng thậm chí sẽ không được gọi.

"Trong ví dụ về Phân số của chúng ta, nếu chúng ta tạo Mã băm bằng với tử số, thì các phân số 2/3 và 4/6 sẽ có các Mã băm khác nhau. Các phân số giống nhau và phương thức bằng cho biết chúng giống nhau, nhưng Mã băm của chúng lại cho biết chúng khác nhau. Và nếu chúng ta so sánh bằng cách sử dụng hashCode trước khi so sánh bằng cách sử dụng bằng, thì chúng ta kết luận rằng các đối tượng là khác nhau và chúng ta thậm chí không bao giờ chuyển sang phương thức bằng."

Đây là một ví dụ:

HashSet<Fraction>set = new HashSet<Fraction>();
set.add(new Fraction(2,3));System.out.println( set.contains(new Fraction(4,6)) );
Nếu phương thức hashCode()  trả về tử số của phân số, kết quả sẽ là  false .
Và đối tượng « new Fraction(4,6) » sẽ không được tìm thấy trong bộ sưu tập.

"Vậy đâu là cách phù hợp để thực hiện hashCode cho phân số?"

"Ở đây bạn cần nhớ rằng các phân số tương đương phải có cùng mã băm."

" Phiên bản 1 : mã băm bằng kết quả của phép chia số nguyên."

"Đối với 7/5 và 6/5, đây sẽ là 1."

"Đối với 4/5 và 3/5, đây sẽ là 0."

"Nhưng tùy chọn này không phù hợp để so sánh các phân số cố tình nhỏ hơn 1. Mã băm (kết quả của phép chia số nguyên) sẽ luôn là 0."

" Phiên bản 2 : mã băm bằng kết quả của phép chia số nguyên của mẫu số cho tử số."

"Tùy chọn này phù hợp với các trường hợp phân số nhỏ hơn 1. Nếu phân số nhỏ hơn 1, thì nghịch đảo của nó lớn hơn 1. Và nếu chúng ta đảo ngược tất cả các phân số, thì phép so sánh sẽ không bị ảnh hưởng."

"Phiên bản cuối cùng của chúng tôi kết hợp cả hai giải pháp:"

public int hashCode()
{
return numerator/denominator + denominator/numerator;
}

Hãy kiểm tra nó bằng cách sử dụng 2/3 và 4/6. Họ nên có mã băm giống hệt nhau:

Phân số 2/3 Phân số 4/6
tử số / mẫu số 2/3 == 0 4/6 == 0
Số tử số 3/2 == 1 6/4 == 1
tử số/mẫu số
+
mẫu số/tử số
0 + 1 == 1 0 + 1 == 1

"Đó là tất cả cho bây giờ."

"Cảm ơn, Ellie. Điều đó thực sự thú vị."