Sự khác biệt giữa toán tử == và phương thức equals trong Java

Toán tử == kiểm tra xem hai giá trị có bằng nhau hay không. Giá trị này có thể là kiểu dữ liệu nguyên thủy hoặc tham chiếu đến đối tượng.

Phương thức equals so sánh như thế nào phụ thuộc vào cách định nghĩa phương thức equals của lớp đó. Trong lớp cơ sở Object, phương thức equals so sánh sự giống nhau của các tham chiếu đối tượng, kết quả tương đương với toán tử ==. Lớp String ghi đè phương thức equals để so sánh chuỗi ký tự có bằng nhau hay không.

Phân tích mã nguồn của ==, equals(), hashCode() trong Java

Trong lập trình Java hoặc phỏng vấn kỹ thuật, thường xuyên gặp phải việc so sánh == và equals(). Dưới đây là tổng hợp từ việc xem xét mã nguồn và kinh nghiệm lập trình thực tế.

1. Toán tử ==

Toán tử == trong Java so sánh địa chỉ của hai đối tượng trong JVM. Rất dễ hiểu. Xem đoạn mã sau:

public class AddressComparison {
    public static void main(String[] args) throws Exception {
        String str1 = "hello";
        String str2 = "hello";
        String str3 = new String("hello");
        System.out.println(str1 == str2);    //    true
        System.out.println(str1 == str3);    //    false
    }
}

Trong đoạn mã trên:

  • (1) str1 == str2 trả về true vì cả str1 và str2 đều là tham chiếu đến cùng một chuỗi hằng "hello", trỏ đến cùng một vùng nhớ nên bằng nhau.
  • (2) str1 == str3 trả về false vì đối tượng được tạo bằng từ khóa new nằm trong heap, str3 là tham chiếu đến biến trong heap, trong khi str1 trỏ đến chuỗi hằng "hello", địa chỉ khác nhau nên không bằng nhau.

2. Phương thức equals()

equals là phương thức trong lớp gốc Object. Mã nguồn như sau:

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

Có thể thấy phương thức equals mặc định trực tiếp gọi == để so sánh địa chỉ đối tượng.

Các lớp con khác nhau có thể ghi đè phương thức này để thực hiện việc so sánh equals giữa hai đối tượng.

Mã nguồn ghi đè phương thức equals trong lớp String như sau:

public boolean equals(Object targetObject) {
    if (this == targetObject) {
        return true;
    }
    if (targetObject instanceof String) {
        String otherStr = (String) targetObject;
        int length = value.length;
        if (length == otherStr.value.length) {
            char[] array1 = value;
            char[] array2 = otherStr.value;
            int index = 0;
            while (length-- != 0) {
                if (array1[index] != array2[index])
                        return false;
                index++;
            }
            return true;
        }
    }
    return false;
}

Từ đoạn mã trên có thể thấy:

  • (1) Phương thức equals trong lớp String trước tiên so sánh địa chỉ, nếu là tham chiếu đến cùng một đối tượng thì rõ ràng là bằng nhau, trả về true.
  • (2) Nếu không phải cùng một đối tượng, phương thức equals lần lượt so sánh từng ký tự bên trong hai chuỗi, chỉ khi hoàn toàn bằng nhau mới trả về true, ngược lại trả về false.

3. Phương thức hashCode()

hashCode là phương thức trong lớp gốc Object.

Mặc định, hashCode() trong Object trả về địa chỉ bộ nhớ 32 bit của đối tượng trong JVM. Tức là nếu đối tượng không ghi đè phương thức này, sẽ trả về địa chỉ bộ nhớ 32 bit tương ứng của đối tượng.

Mã nguồn ghi đè phương thức hashCode trong lớp String như sau:

public int hashCode() {
    int result = cachedHash;    //Mặc định là 0 - biến riêng tư trong lớp String
    if (result == 0 && value.length > 0) {    //private final char value[]; - mảng lưu nội dung chuỗi trong lớp String
        char[] charArray = value;

        for (int pos = 0; pos < value.length; pos++) {
            result = 31 * result + charArray[pos];
        }
        cachedHash = result;
    }
    return result;
}

Lớp String sử dụng private final char value[] để lưu trữ nội dung chuỗi, do đó String là bất biến.

Xem ví dụ sau, lớp không ghi đè phương thức hashCode trực tiếp trả về địa chỉ 32 bit của đối tượng trong JVM; lớp Long ghi đè phương thức hashCode, trả về giá trị hashCode được tính toán:

public class HashCodeExample {
    public static void main(String[] args) throws Exception {
        HashCodeExample obj1 = new HashCodeExample();
        HashCodeExample obj2 = new HashCodeExample();
        System.out.println(obj1.hashCode());    //870919696
        System.out.println(obj2.hashCode());    //298792720
        
        Long number1 = new Long(8);
        Long number2 = new Long(8);
        System.out.println(number1.hashCode());    //8
        System.out.println(number2.hashCode());    //8
    }
}

Kết luận:

(1) Liên kết. Khi phương thức equals được ghi đè, thường cần thiết phải ghi đè phương thức hashCode để duy trì quy tắc thông thường của phương thức hashCode, quy tắc này tuyên bố rằng các đối tượng bằng nhau phải có mã băm bằng nhau.

(2) Lý do liên kết. Hashtable triển khai bảng băm, để thành công lưu trữ và truy xuất đối tượng trong bảng băm, các đối tượng được dùng làm khóa phải triển khai phương thức `hashCode` và phương thức `equals`. Tương tự mục (1), phải đảm bảo rằng các đối tượng equals bằng nhau thì `hashCode` cũng bằng nhau. Vì bảng băm truy xuất đối tượng thông qua hashCode.

(3) Mặc định.

  • Toán tử == mặc định so sánh địa chỉ của đối tượng trong JVM.
  • hashCode mặc định trả về địa chỉ lưu trữ của đối tượng trong JVM.
  • equals so sánh đối tượng, mặc định cũng so sánh địa chỉ của đối tượng trong JVM, giống với ==

Thẻ: Java equality-comparison object-equals hashcode string-comparison

Đăng vào ngày 19 tháng 5 lúc 05:06