1. So sánh các đối tượng trong Java

Trong Java, các đối tượng có thể được so sánh theo cả tham chiếu và giá trị.

So sánh tài liệu tham khảo

Nếu hai biến trỏ đến cùng một đối tượng trong bộ nhớ, thì các tham chiếu được lưu trữ trong các biến này là bằng nhau. Nếu bạn so sánh các biến này bằng toán tử đẳng thức ( ==), bạn sẽ nhận được kết quả đúng và kết quả đó có ý nghĩa. Mọi thứ đều đơn giản ở đây.

Mã số Đầu ra bảng điều khiển
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

So sánh theo giá trị

Nhưng bạn có thể thường xuyên gặp các tình huống trong đó hai biến đề cập đến hai đối tượng riêng biệt giống hệt nhau. Ví dụ: hai đối tượng chuỗi khác nhau chứa cùng một văn bản.

Để xác định xem các đối tượng khác nhau có giống nhau hay không, hãy sử dụng equals()phương thức. Ví dụ:

Mã số Đầu ra bảng điều khiển
String a = new String("Hello");
String b = new String("Hello");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

Phương pháp này equalskhông giới hạn trong Stringlớp. Lớp nào cũng có.

Ngay cả các lớp mà bạn tự viết, và đây là lý do tại sao.



2. Objectlớp học

Tất cả các lớp trong Java đều kế thừa Objectlớp. Những người tạo ra Java đã đưa ra cách tiếp cận này.

Và nếu một lớp kế thừa Objectlớp đó, thì nó sẽ nhận được tất cả các phương thức của Objectlớp đó. Và đây là hệ quả chính của việc thừa kế.

Nói cách khác, mọi lớp đều có các phương thức của Objectlớp đó, ngay cả khi mã của chúng không đề cập đến chúng.

Các phương thức kế thừa này bao gồm các phương thức liên quan đến so sánh đối tượng. Đây là những equals()hashCode()phương pháp.

Mã số Trong thực tế, đây là những gì chúng ta sẽ có:
class Person
{
   String name;
   int age;
}
class Person extends Object
{
   String name;
   int age;

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

   public int hashCode()
   {
      return address_of_object_in_memory; // This is the default implementation, but there may be a different implementation
   }
}

Trong ví dụ trên, chúng tôi đã tạo một Personlớp đơn giản với các tham số tên và tuổi, nhưng không phải là một phương thức duy nhất. Nhưng vì tất cả các lớp kế thừa Objectlớp nên Personlớp tự động có hai phương thức:

Phương pháp Sự miêu tả
boolean equals(Object obj)
So sánh đối tượng hiện tại và đối tượng đã truyền
int hashCode()
Trả về mã băm của đối tượng hiện tại

Nó chỉ ra rằng hoàn toàn mọi đối tượng đều có equalsphương thức và các đối tượng thuộc các loại khác nhau có thể được so sánh với nhau. Mã như vậy sẽ biên dịch và hoạt động hoàn hảo.

Mã số Đầu ra bảng điều khiển
Integer a = 5;
String s = "Hello";
System.out.println(a.equals(s));
System.out.println(s.equals(a));


false
false
Object a = new Integer(5);
Object b = new Integer(5);
System.out.println(a.equals(b)) ;


true

3. equals()phương pháp

Phương equals()thức, kế thừa từ Objectlớp, thực hiện thuật toán đơn giản nhất để so sánh đối tượng hiện tại với các đối tượng được truyền: nó chỉ so sánh các tham chiếu đến các đối tượng.

Bạn sẽ nhận được kết quả tương tự nếu bạn chỉ so sánh Personcác biến thay vì gọi equals()phương thức. Ví dụ:

Mã số Đầu ra bảng điều khiển
Person a = new Person();
a.name = "Steve";

Person b = new Person();
b.name = "Steve";

System.out.println(a == b);
System.out.println(a.equals(b));






false
false

Khi equalsphương thức được gọi on a, nó chỉ so sánh tham chiếu được lưu trong abiến với tham chiếu được lưu trong bbiến.

Tuy nhiên, so sánh hoạt động khác nhau cho Stringlớp. Tại sao?

Bởi vì những người đã tạo ra Stringlớp đã viết cách triển khai phương thức của riêng họ equals().

Thực hiện phương equals()pháp

Bây giờ, hãy viết triển khai phương thức bằng của riêng chúng ta trong lớp Person. Chúng ta sẽ xem xét 4 trường hợp chính.

Quan trọng:
Bất kể lớp nào ghi đè equalsphương thức, nó luôn lấy một Objectđối tượng làm đối số

Tình huống 1 : cùng một đối tượng mà equalsphương thức được gọi cũng được truyền cho equalsphương thức. Nếu các tham chiếu của đối tượng hiện tại và đối tượng được truyền bằng nhau, thì phương thức phải trả về true. Một đối tượng bằng chính nó.

Trong mã nó sẽ trông như thế này:

Mã số Sự miêu tả
public boolean equals(Object obj)
{
   if (this == obj)
    return true;

   // The rest of the code of the equals method
}


So sánh tài liệu tham khảo

Kịch bản 2 : nullđược truyền cho equalsphương thức — chúng tôi không có gì để so sánh. Đối tượng mà equalsphương thức được gọi chắc chắn không phải là null, vì vậy chúng ta cần return falsetrong trường hợp này.

Trong mã nó sẽ trông như thế này:

Mã số Sự miêu tả
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   // The rest of the code of the equals method
}


So sánh tài liệu tham khảo


Là đối tượng thông qua null?

Tình huống 3 : một tham chiếu đến một đối tượng không phải là a Personđược truyền cho equalsphương thức. Đối tượng có Personbằng Personđối tượng không? Đó là câu hỏi để người phát triển lớp Personquyết định theo cách họ muốn.

Nhưng thông thường các đối tượng phải thuộc cùng một lớp để được coi là bằng nhau. Do đó, nếu thứ gì đó không phải là đối tượng của Personlớp được truyền cho phương thức bằng của chúng ta, thì chúng ta sẽ luôn trả về false. Làm thế nào bạn có thể kiểm tra loại đối tượng? Đúng vậy — bằng cách sử dụng instanceoftoán tử.

Đây là mã mới của chúng tôi trông như thế nào:

Mã số Sự miêu tả
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   // The rest of the code of the equals method
}


So sánh tài liệu tham khảo


Là đối tượng thông qua null?


Nếu đối tượng được truyền không phải là mộtPerson

4. So sánh hai Personđối tượng

Chúng ta đã kết thúc với cái gì? Nếu chúng ta đã đến cuối phương thức, thì chúng ta có một Persontham chiếu đối tượng không phải là null. Vì vậy, chúng tôi chuyển đổi nó thành a Personvà so sánh dữ liệu nội bộ có liên quan của cả hai đối tượng. Và đó là kịch bản thứ tư của chúng tôi .

Mã số Sự miêu tả
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   // The rest of the code of the equals method
}


So sánh tài liệu tham khảo


Là đối tượng thông qua null?


Nếu đối tượng được truyền không phải là Person


Typecasting

Và làm thế nào để bạn so sánh hai Personđối tượng? Họ bằng nhau nếu họ có cùng tên ( name) và tuổi ( age). Mã cuối cùng sẽ trông như thế này:

Mã số Sự miêu tả
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   return this.name == person.name && this.age == person.age;
}


So sánh tài liệu tham khảo


Là đối tượng thông qua null?


Nếu đối tượng được truyền không phải là Person


Typecasting

Nhưng đó không phải là tất cả.

Đầu tiên, trường tên là một String, vì vậy bạn cần so sánh trường tên bằng cách gọi equalsphương thức.

this.name.equals(person.name)

Thứ hai, nametrường có thể là null: trong trường hợp đó, bạn không thể gọi equalsnó. Bạn cần kiểm tra thêm cho null:

this.name != null && this.name.equals(person.name)

Điều đó nói rằng, nếu trường tên nằm nulltrong cả hai Personđối tượng, thì tên vẫn bằng nhau.

Mã cho kịch bản thứ tư có thể giống như sau:

Person person = (Person) obj;

if (this.age != person.age)
   return false;

if (this.name == null)
   return person.name == null;

return this.name.equals(person.name);


Nếu các độ tuổi không bằng nhau,
ngay lập tức return false

Nếu this.namebằng null, không có ích gì khi so sánh bằng equalsphương pháp này. Ở đây, trường thứ hai namebằng nullhoặc không.

So sánh hai trường tên bằng equalsphương pháp.


5. hashCode()phương pháp

Ngoài equalsphương pháp nhằm thực hiện so sánh chi tiết tất cả các trường của cả hai đối tượng, còn có một phương pháp khác có thể được sử dụng để so sánh không chính xác nhưng rất nhanh: hashCode().

Hãy tưởng tượng bạn đang sắp xếp theo thứ tự bảng chữ cái một danh sách hàng nghìn từ và bạn cần so sánh nhiều lần các cặp từ. Và các từ dài, bao gồm rất nhiều chữ cái. Nói chung, một so sánh như vậy sẽ mất một thời gian rất dài.

Nhưng nó có thể được tăng tốc. Giả sử chúng ta có những từ bắt đầu bằng các chữ cái khác nhau - rõ ràng là chúng khác nhau ngay lập tức. Nhưng nếu chúng bắt đầu bằng các chữ cái giống nhau, thì chúng ta vẫn chưa thể nói kết quả sẽ như thế nào: các từ có thể giống nhau hoặc khác nhau.

Phương pháp này hashCode()hoạt động bằng cách sử dụng một nguyên tắc tương tự. Nếu bạn gọi nó trên một đối tượng, nó sẽ trả về một số - tương tự như chữ cái đầu tiên trong một từ. Số này có các thuộc tính sau:

  • Các đối tượng giống hệt nhau luôn có cùng mã băm
  • Các đối tượng khác nhau có thể có cùng mã băm hoặc mã băm của chúng có thể khác nhau
  • Nếu các đối tượng có mã băm khác nhau, thì các đối tượng chắc chắn khác nhau

Để làm cho điều này rõ ràng hơn, hãy sắp xếp lại các thuộc tính này dưới dạng từ:

  • Các từ giống hệt nhau luôn có các chữ cái đầu tiên giống nhau.
  • Các từ khác nhau có thể có các chữ cái đầu tiên giống nhau hoặc các chữ cái đầu tiên của chúng có thể khác nhau
  • Nếu các từ có các chữ cái đầu tiên khác nhau, thì các từ chắc chắn khác nhau

Thuộc tính cuối cùng được sử dụng để tăng tốc so sánh các đối tượng:

Đầu tiên, mã băm của hai đối tượng được tính toán. Nếu các mã băm này khác nhau, thì các đối tượng chắc chắn là khác nhau và không cần phải so sánh chúng thêm nữa.

Nhưng nếu các mã băm giống nhau, thì chúng ta vẫn phải so sánh các đối tượng bằng phương thức bằng.



6. Hợp đồng bằng mật mã

Hành vi được mô tả ở trên phải được thực hiện bởi tất cả các lớp trong Java. Trong quá trình biên dịch, không có cách nào để kiểm tra xem các đối tượng có được so sánh chính xác hay không.

Các lập trình viên Java có một thỏa thuận chung rằng nếu họ viết triển khai phương thức equals() của riêng họ và do đó ghi đè lên triển khai tiêu chuẩn (trong lớp Object), thì họ cũng phải viết triển khai phương thức của riêng mình hashCode()theo cách mà các quy tắc đã nói ở trên là phù hợp. thỏa mãn.

Sự sắp xếp này được gọi là một hợp đồng .

Nếu bạn chỉ triển khai equals()hoặc chỉ hashCode()phương thức trong lớp của mình, thì bạn đã vi phạm nghiêm trọng hợp đồng (bạn đã phá vỡ thỏa thuận). Đừng làm điều này.

Nếu các lập trình viên khác sử dụng mã của bạn, nó có thể hoạt động không chính xác. Hơn nữa, bạn sẽ sử dụng mã dựa trên việc tuân thủ các hợp đồng trên.

Quan trọng!

Khi tìm kiếm một phần tử, tất cả các bộ sưu tập Java trước tiên so sánh mã băm của các đối tượng và chỉ sau đó mới thực hiện so sánh bằng phương equalsthức.

Điều đó có nghĩa là nếu bạn cung cấp cho lớp của mình một equalsphương thức nhưng bạn không viết hashCode()phương thức của riêng mình hoặc bạn triển khai nó không chính xác, thì các tập hợp có thể không hoạt động chính xác với các đối tượng của bạn.

Ví dụ: bạn có thể thêm một đối tượng vào danh sách rồi tìm kiếm đối tượng đó bằng contains()phương pháp này, nhưng bộ sưu tập có thể không tìm thấy đối tượng của bạn.