Hashcode và Equals là các phương thức đã được định nghĩa trong lớp Object, là lớp cha của tất cả các lớp trong java. Chính vì thế nên tất cả các đối tượng java kế thừa triển khai mặc định của hai phương thức này.

Tác dụng của phương thức hashcode() và equals()

Equals() : dùng để xác minh sự bằng nhau của hai đối tượng, hai đối tượng bằng nhau khi và chỉ khi nó cùng tham chiếu đến một vị trí ô nhớ. Hầu hết các lớp trong java ghi đè lại phương thức này để tạo ra logic so sánh của riêng nó.

Hashcode() : trả về một giá trị số nguyên duy nhất cho đối tượng trong thời gian chạy, giá trị số nguyên được lấy từ địa chỉ bộ nhớ của đối tượng trong Heap.

Quan hệ giữa hashcode() và equals()

Khi ghi đè lại phương thức equals() thì chúng ta cũng phải ghi đè lại phương thức hashcode(), bởi vì các đối tượng bằng nhau phải có mã băm (hashcode) bằng nhau

Hợp đồng tương quan:

  • Internal consistency : giá trị của hashcode() chỉ thay đổi khi vì chỉ khi có một thuộc tính trong equals() thay đổi, trong cùng một lần chạy.
  • Equals consistency : các đối tượng bằng nhau phải trả về cùng một mã băm.
  • Collisions : các đối tượng không bằng nhau có thể có cùng một mã băm.

Ghi đè equals mà không ghi đè hashcode() có được không?

Theo dõi đoạn code bên dưới, ban đầu chưa ghi đè phương thức hashcode()

public class MainTest {

    public static void main(String[] args) {
        Employee e1 = new Employee();
        Employee e2 = new Employee();

        e1.setId(100);
        e2.setId(100);

        System.out.println(e1.equals(e2));
        System.out.println("e1 hash code: " + e1.hashCode());
        System.out.println("e2 hash code: " + e2.hashCode());
    }

    @Data
    private static class Employee {
        private Integer id;
        private String firstName;
        private String lastName;
        private String department;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Employee employee = (Employee) o;
            return Objects.equals(id, employee.id) &&
                    Objects.equals(firstName, employee.firstName) &&
                    Objects.equals(lastName, employee.lastName) &&
                    Objects.equals(department, employee.department);
        }
    }
}
kết quả của đoạn code trên

Ta thấy ban đầu chưa ghi đè phương thức hashcode(), thì mặc dù khi so sánh e1 và e2 thì nó trả về giá trị true, nhưng cả hai lại trả về hai mã băm khác nhau, vi phạm tương quan hợp đồng giữa equals và hashcode.

public class MainTest {

    public static void main(String[] args) {
        Employee e1 = new Employee();
        Employee e2 = new Employee();

        e1.setId(100);
        e2.setId(100);

        System.out.println(e1.equals(e2));
        System.out.println("e1 hash code: " + e1.hashCode());
        System.out.println("e2 hash code: " + e2.hashCode());
    }

    @Data
    private static class Employee {
        private Integer id;
        private String firstName;
        private String lastName;
        private String department;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Employee employee = (Employee) o;
            return Objects.equals(id, employee.id) &&
                    Objects.equals(firstName, employee.firstName) &&
                    Objects.equals(lastName, employee.lastName) &&
                    Objects.equals(department, employee.department);
        }

        @Override
        public int hashCode() {

            return Objects.hash(id, firstName, lastName, department);
        }
    }
}
kết quả của đoạn code sau khi ghi đè hashcode()

Sau khi ghi đè phương thức hashcode() thì lúc này hai đối tượng e1 và e2 đã có cùng giá trị hashcode, lúc này chúng đã bằng nhau.

EqualsBuilder và HashCodeBuilder

Apache common cung cấp hai lớp tiện ích là EqualsBuilder và HashCodeBuilder để tiện xử lý phương thức equals và hashcode.

public class MainTest {

    public static void main(String[] args) {
        Employee e1 = new Employee();
        Employee e2 = new Employee();

        e1.setId(100);
        e2.setId(100);

        System.out.println(e1.equals(e2));
        System.out.println("e1 hash code: " + e1.hashCode());
        System.out.println("e2 hash code: " + e2.hashCode());
    }

    @Data
    private static class Employee {
        private Integer id;
        private String firstName;
        private String lastName;
        private String department;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Employee employee = (Employee) o;
            return new EqualsBuilder().append(getId(), employee.getId()).isEquals();
        }

        @Override
        public int hashCode() {
            final int PRIME = 31;
            return new HashCodeBuilder(getId() % 2 == 0 ? getId() + 1 : getId(), PRIME).toHashCode();
        }
    }
}
kết quả của đoạn code trên

Phương pháp xử lý tối ưu

  • luôn sử dụng các trường giống nhau để tạo hashcode() và equal()
  • equals() phải đảm bảo tính nhất quán, nếu nó không bị sửa đổi thì đối tượng phải luôn trả về cùng một giá trị hashcode()
  • nếu chúng ta ghi đè lại equals thì cũng phải ghi đè lại hashcode()
  • chỉ nên ghi đè lại các phương thức equals và hashcode nếu chúng ta cần xử lý logic so sánh, nếu không có thể dùng mặc định.

Xem thêm các bài viết liên quan dưới đây: