CyStack logo
  • Sản phẩm & Dịch vụ
  • Giải pháp
  • Bảng giá
  • Công ty
  • Tài liệu
Vi

vi

Trang chủHướng dẫnEquals và hashCode trong Java
Java

Equals và hashCode trong Java

CyStack blog 6 phút để đọc
CyStack blog06/09/2025
Locker Avatar

Bao Tran

Web Developer

Locker logo social
Reading Time: 6 minutes

Các phương thức equals() và hashCode() của Java có trong lớp object. Vì vậy, mọi lớp Java đều có implementation (sự triển khai) mặc định của equals() và hashCode().

Trong bài viết này, chúng ta sẽ xem xét chi tiết các phương thức equals và hashCode trong Java.

Equals và hashCode trong Java

Java equals()

Lớp object đã định nghĩa phương thức equals() như thế này:

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

Theo tài liệu (documentation) của Java về phương thức equals(), mọi implementation nên tuân thủ các nguyên tắc sau:

  • Với bất kỳ đối tượng nào x, x.equals(x) phải trả về true.
  • Với bất kỳ hai đối tượng x và y, x.equals(y) sẽ trả về true nếu và chỉ nếu y.equals(x) trả về true.
  • Với nhiều đối tượng x, y và z, nếu x.equals(y) trả về true và y.equals(z) trả về true, thì x.equals(z) phải trả về true.
  • Nhiều lần gọi x.equals(y) phải trả về cùng một kết quả, trừ khi bất kỳ properties (thuộc tính) nào của đối tượng được sửa đổi và được sử dụng trong implementation của phương thức equals().
  • implementation của phương thức equals() trong lớp Object chỉ trả về true khi cả hai references (tham chiếu) cùng trỏ đến cùng một đối tượng.

Java hashCode()

Phương thức hashCode() của Java Object là một native method (phương thức gốc) và trả về giá trị hash code (mã băm) số nguyên của đối tượng. Quy tắc chung của phương thức hashCode() là:

  • Nhiều lần gọi hashCode() phải trả về cùng một giá trị số nguyên, trừ khi property của đối tượng được sửa đổi và được sử dụng trong phương thức equals().
  • Giá trị hash code của một đối tượng có thể thay đổi trong nhiều lần thực thi của cùng một ứng dụng.
  • Nếu hai đối tượng bằng nhau theo phương thức equals(), thì hash code của chúng phải giống nhau.
  • Nếu hai đối tượng không bằng nhau theo phương thức equals(), hash code của chúng không nhất thiết phải khác nhau. Giá trị hash code của chúng có thể bằng hoặc không bằng nhau.

Tầm quan trọng của phương thức equals()hashCode()

Các phương thức hashCode() và equals() của Java được sử dụng trong các implementation dựa trên Hash table trong Java để lưu trữ và truy xuất dữ liệu. Tôi đã giải thích chi tiết điều đó tại “Cách HashMap hoạt động trong Java?”. implementation của equals() và hashCode() phải tuân theo các quy tắc này.

  • Nếu o1.equals(o2), thì o1.hashCode() == o2.hashCode() phải luôn luôn là true.
  • Nếu o1.hashCode() == o2.hashCode là true, điều đó không có nghĩa là o1.equals(o2) sẽ là true.

Khi nào nên ghi đè các phương thức equals()hashCode()?

Khi chúng ta override (ghi đè) phương thức equals(), gần như là cần thiết phải override cả phương thức hashCode() để contract của chúng không bị vi phạm bởi implementation của chúng ta. Lưu ý rằng chương trình của bạn sẽ không ném bất kỳ exceptions (ngoại lệ) nào nếu contract của equals() và hashCode() bị vi phạm, nếu bạn không có kế hoạch sử dụng lớp như key của Hash table, thì nó sẽ không gây ra bất kỳ vấn đề nào. Nếu bạn có kế hoạch sử dụng một lớp như key của Hash table, thì việc override cả hai phương thức equals() và hashCode() là điều bắt buộc. Hãy xem điều gì sẽ xảy ra khi chúng ta dựa vào implementation mặc định của các phương thức equals() và hashCode() và sử dụng một lớp tùy chỉnh làm key của HashMap.

package com.journaldev.java;

public class DataKey {

	private String name;
	private int id;

        // getter and setter methods

	@Override
	public String toString() {
		return "DataKey [name=" + name + ", id=" + id + "]";
	}

}
package com.journaldev.java;

import java.util.HashMap;
import java.util.Map;

public class HashingTest {

	public static void main(String[] args) {
		Map<DataKey, Integer> hm = getAllData();

		DataKey dk = new DataKey();
		dk.setId(1);
		dk.setName("Pankaj");
		System.out.println(dk.hashCode());

		Integer value = hm.get(dk);

		System.out.println(value);

	}

	private static Map<DataKey, Integer> getAllData() {
		Map<DataKey, Integer> hm = new HashMap<>();

		DataKey dk = new DataKey();
		dk.setId(1);
		dk.setName("Pankaj");
		System.out.println(dk.hashCode());

		hm.put(dk, 10);

		return hm;
	}

}

Khi chúng ta chạy chương trình trên, nó sẽ in ra null. Đó là vì phương thức hashCode() của Object được sử dụng để tìm bucket để tìm key. Vì chúng ta không có quyền truy cập vào các keys của HashMap và chúng ta đang tạo lại key để truy xuất dữ liệu, bạn sẽ nhận thấy rằng giá trị hash code của cả hai đối tượng là khác nhau và do đó không tìm thấy giá trị.

Implementing phương thức equals()hashCode()

Chúng ta có thể tự định nghĩa implementation của phương thức equals() và hashCode(), nhưng nếu chúng ta không implement chúng một cách cẩn thận, nó có thể gây ra các vấn đề kỳ lạ trong thời gian chạy. May mắn thay, hầu hết các IDE ngày nay đều cung cấp các cách để implement chúng một cách tự động và nếu cần, chúng ta có thể thay đổi chúng theo yêu cầu của mình. Chúng ta có thể sử dụng Eclipse để tự động tạo các phương thức equals() và hashCode().

eclipse generate hashcode equals

Đây là các implementation của phương thức equals() và hashCode() được tạo tự động.

@Override
public int hashCode() {
	final int prime = 31;
	int result = 1;
	result = prime * result + id;
	result = prime * result + ((name == null) ? 0 : name.hashCode());
	return result;
}

@Override
public boolean equals(Object obj) {
	if (this == obj)
		return true;
	if (obj == null)
		return false;
	if (getClass() != obj.getClass())
		return false;
	DataKey other = (DataKey) obj;
	if (id != other.id)
		return false;
	if (name == null) {
		if (other.name != null)
			return false;
	} else if (!name.equals(other.name))
		return false;
	return true;
}

Lưu ý rằng cả hai phương thức equals() và hashCode() đều sử dụng cùng các trường cho các tính toán, để contract của chúng vẫn hợp lệ. Nếu bạn chạy lại chương trình kiểm tra, chúng ta sẽ nhận được đối tượng từ map và chương trình sẽ print ra 10. Chúng ta cũng có thể sử dụng Project Lombok để tự động tạo implementations của phương thức equals và hashCode.

Hash Collision là gì?

Nói một cách rất đơn giản, các implementation của Java Hash table sử dụng logic sau cho các thao tác get và put:

  • Trước tiên, hãy xác định “Bucket” để sử dụng bằng cách sử dụng hash code của “key”.
  • Nếu không có đối tượng nào có mặt trong bucket có cùng hash code, thì hãy thêm đối tượng cho thao tác put và trả về null cho thao tác get.
  • Nếu có các đối tượng khác trong bucket có cùng hash code, thì phương thức equals() của “key” sẽ bắt đầu hoạt động.
    • Nếu equals() trả về true và đó là thao tác put, thì value của đối tượng sẽ bị override.
    • Nếu equals() trả về false và đó là thao tác put, thì một entry (mục nhập) mới sẽ được thêm vào bucket.
    • Nếu equals() trả về true và đó là thao tác get, thì value của đối tượng sẽ được trả về.
    • Nếu equals() trả về false và đó là thao tác get, thì null sẽ được trả về.

Hình ảnh dưới đây cho thấy các items bucket của HashMap và cách equals() và hashCode() của chúng liên quan đến nhau.

eclipse generate hashcode equals

Hiện tượng khi hai keys có cùng hash code được gọi là hash collision (xung đột băm). Nếu phương thức hashCode() không được implement đúng cách, sẽ có số lượng hash collision cao hơn và các entries của map sẽ không được phân phối đúng cách, gây ra sự chậm trễ trong các thao tác get và put. Đây là lý do để sử dụng số nguyên tố trong việc tạo hash code để các entries của map được phân phối đúng cách trên tất cả các buckets.

Điều gì sẽ xảy ra nếu chúng ta không implement cả hashCode()equals()?

Chúng ta đã thấy ở trên rằng nếu hashCode() không được implement, chúng ta sẽ không thể truy xuất value vì HashMap sử dụng hash code để tìm bucket để tìm entry. Nếu chúng ta chỉ sử dụng hashCode() và không implement equals() thì value cũng sẽ không được truy xuất vì phương thức equals() sẽ trả về false.

Các phương pháp hay nhất để implement phương thức equals()hashCode()

  • Sử dụng cùng các properties trong cả implementations của phương thức equals() và hashCode(), để contract của chúng không bị vi phạm khi bất kỳ properties nào được cập nhật.
  • Tốt hơn là nên sử dụng các đối tượng immutable (bất biến) làm key của Hash table để chúng ta có thể cache (lưu vào bộ nhớ đệm) hash code thay vì tính toán nó trên mỗi lần gọi. Đó là lý do tại sao String là một ứng cử viên tốt cho key của Hash table vì nó immutable và cache giá trị hash code.
  • Implement phương thức hashCode() để xảy ra ít hash collision nhất và các entries được phân phối đồng đều trên tất cả các buckets. Bạn có thể download (tải xuống) toàn bộ code từ GitHub Repository của chúng tôi.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất