Cách viết bằng() trong Java

Feb 22 2023
Đến cuối bài viết này, tất cả các thông tin cần thiết sẽ được đề cập để có thể viết phương thức equals() tốt nhất trong ngôn ngữ lập trình Java. Các chi tiết về đẳng thức sẽ được xử lý theo định nghĩa toán học và theo cách của ngôn ngữ lập trình Java để bao quát thông tin cơ bản cần thiết để sử dụng phương pháp thực hành tốt nhất của phương thức equals().

Đến cuối bài viết này, tất cả các thông tin cần thiết sẽ được đề cập để có thể viết phương thức equals() tốt nhất trong ngôn ngữ lập trình Java. Các chi tiết về đẳng thức sẽ được xử lý theo định nghĩa toán học và theo cách của ngôn ngữ lập trình Java để bao quát thông tin cơ bản cần thiết để sử dụng phương pháp thực hành tốt nhất của phương thức equals( ) .

Định nghĩa toán học của đẳng thức

Theo Wikipedia , định nghĩa của đẳng thức toán học là

Trong toán học, đẳng thức là mối quan hệ giữa hai đại lượng hoặc nói chung là hai biểu thức toán học, khẳng định rằng các đại lượng có cùng giá trị hoặc các biểu thức biểu thị cùng một đối tượng toán học. Đẳng thức giữa A và B được viết A = B , và phát âm là A bằng B .

Định nghĩa bình đẳng trong Java

Trong ngôn ngữ lập trình Java, đẳng thức có nghĩa đơn giản là có cùng một giá trị. Nó đôi khi được hiểu theo nghĩa đen là có cùng giá trị và đôi khi được hiểu là có cùng cấu trúc xét về các thuộc tính của đối tượng.

Khi cần kiểm tra sự bằng nhau của hai giá trị kiểu nguyên thủy, dấu bằng được sử dụng trực tiếp như trong ví dụ bên dưới;

int a = 5;
int b = 6;
int c = 5;

// Prints "false" since a and b do not have the same value.
System.out.println(a == b);

// Prints "true" since a and c have the same value.
System.out.println(a == c);

Mặt khác, đôi khi cần kiểm tra sự bằng nhau của hai đối tượng. Về đối tượng, có hai cách tiếp cận khác nhau được sử dụng trong các giai đoạn khác nhau của quá trình kiểm tra sự bằng nhau của hai đối tượng. Hãy đi vào chi tiết.

bình đẳng tham chiếu

Trong ngôn ngữ lập trình Java, các giá trị thực của đối tượng được lưu trữ ở đâu đó trong bộ nhớ và chúng được chỉ ra bởi các tham chiếu đối tượng cũng được lưu trữ ở đâu đó trong bộ nhớ. Hãy xem đoạn mã sau;

// Create an object with "new" operator.
// Point to object with object reference "a".
Object a = new Object();

// Create another object reference.
// Point to the same object as the reference "a" is pointing.
Object b = a;

// Output is true.
System.out.println(a == b);

// Create another object with "new" operator.
// Point to object with object reference "b".
b = new Object();

// Output is false.
System.out.println(a == b);

Trong trường hợp như vậy, “a” và “b” thực sự là hai tham chiếu trỏ đến cùng một đối tượng, vì vậy có thể nói trực tiếp rằng “a” bằng “b” vì có được đẳng thức tham chiếu.

Tuy nhiên, kịch bản ngược lại không phải lúc nào cũng đúng. Hai tham chiếu khác nhau có thể chỉ các vị trí bộ nhớ khác nhau, nhưng các đối tượng mà điểm tham chiếu vẫn có thể bằng nhau.

Trong cuộc sống bình thường, đôi khi số lượng được so sánh. Như nếu bạn có hai quả táo và bạn của bạn có hai quả táo thì số táo của bạn và bạn của bạn bằng nhau.

Mặt khác, đôi khi mọi thứ cũng có thể được so sánh về tính chất hoặc cấu trúc của nó. Hãy đi vào chi tiết cho kịch bản này.

bình đẳng cơ cấu

Bình đẳng cấu trúc là kiểu mà các đối tượng được so sánh theo các trạng thái thuộc tính của chúng. Cần có một công cụ phức tạp hơn để kiểm tra loại đẳng thức này thay vì chỉ kiểm tra toán tử “==”. Đó là phương thức equals(). Nó xuất phát từ lớp Đối tượng ở điểm cao nhất của hệ thống phân cấp lớp trong ngôn ngữ lập trình Java. Hãy kiểm tra đoạn mã Java sắp tới;

public class Car {

    // Assume that we have corresponding constructor, accessors, mutators, etc.

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;
    
}

Car car1 = new Car("Audi","R8", 2023);
Car car2 = new Car("Audi","R8", 2023);
Car car3 = new Car("Audi","R8", 2021);

System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));

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

public class Car {

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;

    // Assume that we have corresponding constructor, accessors, mutators, etc.

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o; 
        return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }
    
}

Trên dòng thứ hai, chúng tôi bắt đầu kiểm tra danh tính của các đối tượng. Nếu tham chiếu đối tượng o là null hoặc cả hai tham chiếu đối tượng đều trỏ đến các đối tượng thuộc các lớp khác nhau, thì có thể nói trực tiếp rằng hai đối tượng này không có cùng một danh tính và không bằng nhau. Nếu đây không phải là trường hợp thì cuối cùng chúng ta cũng có thể kiểm tra cấu trúc thực tế của các đối tượng.

Ở dòng thứ ba của phương thức, chúng ta chuyển tham chiếu o sang loại lớp tương ứng vì phương thức này được ghi đè từ lớp Object và bây giờ chúng ta biết rằng nó thuộc về lớp Car.

Cuối cùng, trên dòng cuối cùng của phương thức, cấu trúc của hai đối tượng được kiểm tra và đây là dòng thực tế mà danh tính được xác định. Đơn giản, nếu mẫu mã thương hiệu và năm sản xuất giống nhau thì hai đối tượng này bằng nhau. Nói cách khác, đây là sự bình đẳng về cấu trúc.

Chúng ta đã học các tính chất của đẳng thức toán học ở tiểu học. Một số thuộc tính này cũng phải hợp lệ đối với phương thức equals() . Điều đó có nghĩa là các thuộc tính này phải được thêm vào các thành phần của phương thức equals() . Hãy xem các tính chất này dưới dạng đẳng thức toán học và về đẳng thức trong Java;

Tài sản phản xạ

Trong định nghĩa toán học của tính chất phản xạ, với mọi số thực x ;

x = x

Một số bằng chính nó.

Xét về đẳng thức Java, một đối tượng phải bằng chính nó;

Car car1 = new Car("Audi","R8",2013);
System.out.println(car1.equals(car1));

@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o; 
        return mileage < car.mileage && brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }

Thuộc tính đối xứng

Về định nghĩa toán học của tính chất đối xứng, với mọi số thực x và y ;

Nếu x = y thì y = x

Thứ tự bình đẳng không quan trọng.

Xét về đẳng thức Java, nếu đối tượng a bằng đối tượng b, thì đối tượng b phải bằng đối tượng a;

Car car1 = new Car("Audi","R8",2013);
Car car2 = new Car("Audi","R8",2013);
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car1));

public class Car {

    private String brand;
    private String model;
    private Integer modelYear;
    private Integer mileage;

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Car)) return false;
        Car car = (Car) o;
        return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear);
    }
}

public class SportCar extends Car {
    
    private String engineType;

    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof SportCar)) return false;
        SportCar sportCar = (SportCar) o;
        return engineType.equals(sportCar.engineType);
    }
}

Car car1 = new Car("Audi", "R8", 2012, 1000);
SportCar car2 = new SportCar("Audi", "R8", 2012, 1000, "4.0");
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car1));

tài sản chuyển tiếp

Trong định nghĩa toán học của tính chất bắc cầu, với mọi số thực x , y và z ;

nếu x = y và y = z thì x = z

Hai số bằng nhau thì bằng nhau.

Về đẳng thức Java, nếu đối tượng a bằng đối tượng b và đối tượng b bằng đối tượng c, thì đối tượng a phải bằng đối tượng c;

SportCar car1 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("100RPM"));
Car car2 = new Car("Audi", "R8", 2012, 1000);
SportCar car3 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("101RPM"));
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));
System.out.println(car1.equals(car3));

public class SportCar extends Car {
    
    private Turbo turbo;

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Car)) return false;
        if (!(o instanceof SportCar)) return o.equals(this);
        return super.equals(o) && ((SportCar) o).turbo == turbo;
    }
}

public class Turbo {
    private String power;

    public Turbo(String power) {
        this.power = power;
    }
}

SportCar car1 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("100RPM"));
Car car2 = new Car("Audi", "R8", 2012, 1000);
SportCar car3 = new SportCar("Audi", "R8", 2012, 1000, new Turbo("101RPM"));
System.out.println(car1.equals(car2));
System.out.println(car2.equals(car3));
System.out.println(car1.equals(car3));

tài sản nhất quán

Đây là một tính chất khác vì không có tính chất tương đương chính xác về mặt toán học. Đối với điều này, chúng ta cần nhận được cùng một kết quả cho các lần thực thi liên tiếp của phương thức equals().

Hãy xem đoạn mã dưới đây;

Car car1 = new Car("Audi", "R8", 2012, 1000);
Car car2 = new Car("Audi", "R8", 2012, 1000);

for (int i = 0; i < 100000; i++) {
    System.out.println(car1.equals(car2));
}

@Override
public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof Car)) return false;
    Car car = (Car) o;
    Random random = new Random();
    return brand.equals(car.brand) && model.equals(car.model) && (random.nextInt(10) + 2013) == car.modelYear;
}

Lưu ý: Ví dụ này không thực sự làm tôi hài lòng. Sau khi nghiên cứu sâu rộng, tôi sẽ thay thế nó bằng một cái tốt hơn. Trong tài liệu Java của phương thức equals() có nói rõ ràng rằng “nhiều lệnh gọi x.equals(y)trả về nhất quán truehoặc trả về nhất quán false”. Phải có một ví dụ chắc chắn về sự vi phạm cho tuyên bố này.

Thuộc tính tham số null

Đây là cách tôi chọn để gọi thuộc tính này.

Vui lòng xem đoạn mã dưới đây;

Car car1 = new Car("Audi", "R8", 2012, 1000);
Car car2 = null;
System.out.println(car1.equals(car2));

Bây giờ, rõ ràng là việc triển khai phương thức equals() nên có những thuộc tính nào để cung cấp một phép tính chính xác về đẳng thức. Ngoài ra, chúng tôi đã thấy các tình huống vi phạm có thể xảy ra, vì vậy giờ đây chúng tôi có thể tránh chúng vì lợi ích của chất lượng mã và logic kinh doanh.

Ngày nay, nhiều IDE cung cấp các cơ chế tạo để tạo ra các triển khai phương thức equals() tốt nhất có thể . Bạn nên gắn bó với chúng hầu hết thời gian thay vì triển khai phương thức equals() . Hãy xem một ví dụ từ Intellij IDEA ;

@Override
   public boolean equals(Object o) {
       if (this == o) return true;
       if (o == null || getClass() != o.getClass()) return false;
       Car car = (Car) o;
       return brand.equals(car.brand) && model.equals(car.model) && modelYear.equals(car.modelYear) && mileage.equals(car.mileage);
   }

Trên dòng đầu tiên, đẳng thức tham chiếu được kiểm tra, bởi vì hai tham chiếu đối tượng có thể trỏ đến cùng một đối tượng.

Trên dòng thứ hai, việc xác thực Thuộc tính tham số Null được đảm bảo và cũng tương tự nếu điều kiện hoạt động như một mệnh đề bảo vệ if bằng cách kiểm soát loại lớp của các đối tượng được chỉ ra bởi các tham chiếu để ngăn vi phạm Thuộc tính bắc cầu và Thuộc tính đối xứng .

Ở dòng thứ ba, tham chiếu đối tượng được chuyển thành loại lớp tương ứng là Car trong ví dụ của chúng ta để đạt được các thuộc tính của đối tượng Car.

Cuối cùng, ở dòng thứ tư, đẳng thức được kiểm tra theo đẳng thức cấu trúc.

Có các mẫu khác để tạo phương thức equals() . Bạn cũng có thể đi và kiểm tra chúng.

Nếu bạn muốn tự mình viết phần triển khai, xin lưu ý rằng việc triển khai phải đảm bảo tính hợp lệ cho tất cả thuộc tính phương thức equals() .

Khi chúng ta đi đến phần cuối của bài viết, vẫn còn một lỗ hổng rất quan trọng cần được giải quyết. Mở IDE của bạn và thử tạo phương thức equals() . Bạn sẽ thấy rằng không có tùy chọn nào để chỉ tạo phương thức equals() mà không có phương thức hashCode() . Do một số kiểu tập hợp trong ngôn ngữ lập trình Java, nên tốt nhất là không nên tách biệt hai phương thức này với nhau. Trong tài liệu Java chính thức cũng có nói rằng nói chung cần phải ghi đè phương thức hashCode() bất cứ khi nào phương thức equals() bị ghi đè. Tôi muốn cung cấp thông tin tương ứng về phương pháp băm và hashCode() trong một bài viết khác của tôi.

Ngoài ra, đôi khi chúng ta có thể không cần phải sử dụng các thuộc tính phương thức equals() cho một số trường hợp đặc biệt như các lớp thực thể của JPA. Nó cũng xứng đáng với bài viết của riêng mình.

Tóm lại là

Tất cả các điểm quan trọng để triển khai đúng phương thức equals() đều được đề cập trong bài viết này. Tôi cũng khuyên bạn nên viết các bài kiểm tra đơn vị tương ứng để kiểm tra tính hợp lệ của các thuộc tính phương thức equals() . Đừng quên xem các tài nguyên khác có thể được thiết lập trên web.

Đầu tiên, nó có vẻ như là một chủ đề rất đơn giản và nguyên thủy vì nó thuộc về các nguyên tắc cơ bản của thiết kế hướng đối tượng. Tuy nhiên, như đã thấy từ các chi tiết, điều rất quan trọng là không bỏ qua các khái niệm cơ bản này và hiểu đầy đủ.