1.Giới thiệu

So sánh các đối tượng là một tính năng thiết yếu của ngôn ngữ lập trình hướng đối tượng.

Trong hướng dẫn này, chúng ta sẽ tìm hiểu một số tính năng của ngôn ngữ Java cho phép chúng ta so sánh các đối tượng. Chúng ta cũng sẽ xem xét các tính năng như vậy trong các thư viện bên ngoài.

2.Toán tử == và !=

Hãy bắt đầu với toán tử ==!= , hai toán tử này có thể cho biết hai đối tượng Java tương ứng có giống nhau hay không.

2.1.Kiểu dữ liệu nguyên thuỷ

Đối với các kiểu dữ liệu nguyên thủy, giống nhau có nghĩa là có các giá trị bằng nhau:

assertThat(1 == 1).isTrue();

Nhờ tính năng auto-unboxing, tính năng này cũng hoạt động khi so sánh giá trị nguyên thủy đối với các kiểu lớp trình bao bọc của nó:

Integer a = new Integer(1);
assertThat(1 == a).isTrue();

Nếu hai số nguyên có các giá trị khác nhau, toán tử == sẽ trả về false, trong khi toán tử != sẽ trả về true.

2.1 Đối tượng

Giả sử chúng tôi muốn so sánh hai loại số nguyên kiểu lớp trình bao bọc có cùng giá trị:

Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a == b).isFalse();

Bằng cách so sánh hai đối tượng, giá trị của các đối tượng đó không phải là 1. Thay vào đó, địa chỉ bộ nhớ của chúng trong ngăn xếp là khác nhau, vì cả hai đối tượng đều được tạo bằng toán tử mới. Nếu chúng ta gán a cho b, thì chúng ta sẽ có một kết quả khác:

Integer a = new Integer(1);
Integer b = a;

assertThat(a == b).isTrue();

Bây giờ chúng ta hãy xem điều gì sẽ xảy ra khi chúng ta sử dụng phương thức Integer#valueOf factory:

Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(1);

assertThat(a == b).isTrue();

Trong trường hợp này, chúng được coi là giống nhau. Điều này là do phương thức valueOf () lưu trữ số nguyên trong bộ nhớ cache để tránh tạo quá nhiều đối tượng
lớp trình bao bọc có cùng giá trị. Do đó, phương thức trả về cùng một thể hiện số nguyên cho cả hai lần gọi.

Java cũng làm điều này đối với String:

assertThat("Hello!" == "Hello!").isTrue();

Tuy nhiên, nếu chúng được tạo bằng toán tử mới, thì chúng sẽ không giống nhau.

Cuối cùng, hai tham chiếu null được coi là giống nhau, trong khi bất kỳ đối tượng nào không phải null được coi là khác với null:

assertThat(null == null).isTrue();

assertThat("Hello!" == null).isFalse();

Tất nhiên, hành vi của các toán tử bình đẳng có thể bị hạn chế. Điều gì sẽ xảy ra nếu chúng ta muốn so sánh hai đối tượng được ánh xạ tới các địa chỉ khác nhau nhưng chúng vẫn được coi là bằng nhau dựa trên trạng thái bên trong của chúng? Chúng ta sẽ xem cách thực hiện điều này trong các phần tiếp theo.

3.So sánh đối tượng bằng phương thức #Equals()

Bây giờ chúng ta hãy nói về một khái niệm rộng hơn của sự bình đẳng với phương thức equals ().

Phương thức này được định nghĩa trong lớp Object để mọi đối tượng Java kế thừa nó. Theo mặc định, việc triển khai của nó so sánh các địa chỉ bộ nhớ đối tượng, vì vậy nó hoạt động giống như toán tử ==. Tuy nhiên, chúng ta có thể ghi đè phương thức này để xác định ý nghĩa của bình đẳng đối với các đối tượng mà chúng ta muốn so sánh.

Đầu tiên, hãy xem nó hoạt động như thế nào đối với các đối tượng hiện có như Integer:

Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a.equals(b)).isTrue();

Phương thức vẫn trả về true khi cả hai đối tượng đều giống nhau.

Chúng ta nên lưu ý rằng chúng ta có thể truyền một đối tượng null làm đối số của phương thức, nhưng không phải là đối tượng mà chúng ta gọi là phương thức.

Chúng ta cũng có thể sử dụng phương thức equals() với một đối tượng của riêng chúng ta. Giả sử chúng ta có một lớp Person:

public class Person {
    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Chúng ta có thể ghi đè phương thức equals() cho lớp này để có thể so sánh hai người dựa trên thuộc tính dữ liệu của chúng:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person that = (Person) o;
    return firstName.equals(that.firstName) &&
      lastName.equals(that.lastName);
}

Để biết thêm thông tin, hãy xem bài viết của chúng tôi về chủ đề này.

4.Phương thức static trong so sánh đối tượng

Bây giờ chúng ta hãy xem xét việc so sánh đối tượng trong phương thức static. Chúng ta đã đề cập trước đó rằng chúng ta không thể sử dụng null làm giá trị của đối tượng đầu tiên, nếu không một NullPointerException sẽ được ném ra.

Phương thức equals() của lớp Objects helper giải quyết vấn đề đó. Nó nhận hai đối số và so sánh chúng, cũng xử lý các giá trị null.

Hãy so sánh các đối tượng Person một lần nữa:

Person joe = new Person("Joe", "Portman");
Person joeAgain = new Person("Joe", "Portman");
Person natalie = new Person("Natalie", "Portman");

assertThat(Objects.equals(joe, joeAgain)).isTrue();
assertThat(Objects.equals(joe, natalie)).isFalse();

Như chúng tôi đã giải thích, phương thức này xử lý các giá trị null. Do đó, nếu cả hai đối số là null, nó sẽ trả về true và nếu chỉ một trong số chúng là null, nó sẽ trả về false.

Điều này có thể thực sự tiện dụng. Giả sử chúng ta muốn thêm ngày sinh tùy chọn vào lớp Person :

public Person(String firstName, String lastName, LocalDate birthDate) {
    this(firstName, lastName);
    this.birthDate = birthDate;
}

Sau đó, chúng ta phải cập nhật phương thức equals (), nhưng với xử lý null. Chúng ta có thể làm điều này bằng cách thêm điều kiện vào phương thức equals () :

birthDate == null ? that.birthDate == null : birthDate.equals(that.birthDate);

Tuy nhiên, nếu chúng ta thêm quá nhiều trường nullable vào lớp của mình, nó có thể trở nên khá lộn xộn. Sử dụng phương thức Objects # equals trong triển khai equals()
sẽ gọn gàng hơn nhiều và cải thiện khả năng đọc:

Objects.equals(birthDate, that.birthDate);

5. Comparable Interface

Logic so sánh cũng có thể được sử dụng để đặt các đối tượng theo một thứ tự cụ thể. Comparable Interface cho phép chúng ta xác định thứ tự giữa các đối tượng bằng cách xác định xem một đối tượng lớn hơn, bằng hoặc nhỏ hơn một đối tượng khác.

Comparable Interface là generic và chỉ có một phương thức, CompareTo (), nhận một đối số của kiểu chung và trả về một int. Giá trị trả về là âm nếu giá trị này thấp hơn đối số, 0 nếu chúng bằng nhau và dương ngược lại.

Giả sử, trong lớp Person của chúng ta, chúng ta muốn so sánh các đối tượng Person theo họ của chúng:

public class Person implements Comparable<Person> {
    //...

    @Override
    public int compareTo(Person o) {
        return this.lastName.compareTo(o.lastName);
    }
}

Phương thứcCompareTo () sẽ trả về một số âm nếu được gọi với một Person có họ lớn hơn giá trị này, 0 nếu trùng họ và dương ngược lại.

Để biết thêm thông tin, hãy xem bài viết của chúng tôi về chủ đề này.

6.Comparator Interface

Comparator Interfacegeneric và có một phương thức so sánh nhận hai đối số của kiểu generic đó và trả về một số nguyên. Chúng ta đã thấy mẫu này trước đó với Comparable Interface.

Comparator là tương tự; tuy nhiên, nó được tách ra khỏi định nghĩa của lớp. Do đó, chúng ta có thể xác định bao nhiêu Comparators mà chúng ta muốn cho một lớp, nơi chúng ta chỉ có thể cung cấp một triển khai So sánh.

Hãy tưởng tượng chúng ta có một trang web hiển thị mọi người trong chế độ xem bảng và chúng ta muốn cung cấp cho người dùng khả năng sắp xếp theo tên thay vì họ. Điều này không thể thực hiện được với Comparable nếu chúng tôi cũng muốn duy trì triển khai hiện tại của mình, nhưng chúng ta có thể triển khai Comparators của riêng mình.

Hãy tạo một Person Comparator sẽ so sánh chỉ bằng tên của họ:

Comparator<Person> compareByFirstNames = Comparator.comparing(Person::getFirstName);

Bây giờ, hãy sắp xếp Danh sách những người sử dụng Comparator đó:

Person joe = new Person("Joe", "Portman");
Person allan = new Person("Allan", "Dale");

List<Person> people = new ArrayList<>();
people.add(joe);
people.add(allan);

people.sort(compareByFirstNames);

assertThat(people).containsExactly(allan, joe);

Ngoài ra còn có các phương thức khác trên giao diện Comparator mà chúng ta có thể sử dụng trong việc triển khai CompareTo ():

@Override
public int compareTo(Person o) {
    return Comparator.comparing(Person::getLastName)
      .thenComparing(Person::getFirstName)
      .thenComparing(Person::getBirthDate, Comparator.nullsLast(Comparator.naturalOrder()))
      .compare(this, o);
}

Trong trường hợp này, trước tiên chúng ta sẽ so sánh họ, sau đó là tên. Tiếp theo, chúng tôi so sánh ngày tháng năm sinh, nhưng vì chúng không có giá trị, chúng tôi phải nói cách xử lý điều đó. Để làm điều này, chúng tôi đưa ra đối số thứ hai để nói rằng chúng nên được so sánh theo thứ tự tự nhiên của chúng, với các giá trị null sẽ là giá trị cuối cùng.

7.Apache Commons

Hãy cùng xem thư viện Apache Commons. Trước hết, hãy nhập phần Maven dependency:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

7.1. ObjectUtils#notEqual Method

Đầu tiên, hãy nói về phương thức ObjectUtils#notEqual. Cần có hai đối số Đối tượng để xác định xem chúng có không bằng nhau hay không, theo cách triển khai phương thức equals () của riêng chúng. Nó cũng xử lý các giá trị null.

Hãy sử dụng lại các ví dụ về Chuỗi của chúng tôi:

String a = new String("Hello!");
String b = new String("Hello World!");

assertThat(ObjectUtils.notEqual(a, b)).isTrue();

Cần lưu ý rằng ObjectUtils có phương thứcequals () .Tuy nhiên, điều đó không được chấp nhận kể từ Java 7, khi Objects#equals xuất hiện

7.2.Phương thức ObjectUtils#compare

Bây giờ, hãy so sánh thứ tự đối tượng với phương thức ObjectUtils#compare. Đó là một phương thức chung sử dụng hai đối số Comparable của kiểu chung đó và trả về một Integer.

Hãy xem nó bằng cách sử dụng Strings một lần nữa:

String first = new String("Hello!");
String second = new String("How are you?");

assertThat(ObjectUtils.compare(first, second)).isNegative();

Theo mặc định, phương thức xử lý các giá trị null bằng cách coi chúng lớn hơn. Nó cũng cung cấp một phiên bản quá tải cung cấp để đảo ngược hành vi đó và coi chúng nhỏ hơn, lấy một đối số boolean.

8. Kết luận

Trong bài viết này, chúng ta đã tìm hiểu các cách khác nhau để so sánh các đối tượng trong Java. Chúng tôi đã kiểm tra sự khác biệt giữa sự giống nhau, bình đẳng và thứ tự. Chúng tôi cũng đã xem xét các tính năng tương ứng trong các thư viện Apache Commons .

Như thường lệ, bạn có thể tìm thấy mã đầy đủ cho bài viết này trên GitHub.


Tham khảo tại: *https://www.baeldung.com/java-comparing-objects