모든 Java 객체에는 equals()와 hashCode()라는 매우 중요한 두 가지 메서드가 있으며, 이 메서드들은 특정 일반 계약(general contract)에 따라 재정의(오버라이드)하도록 설계되어 있습니다. Object 클래스는 모든 클래스의 최상위 부모 클래스이므로, 이 두 메서드의 기본 구현은 이미 모든 클래스에 포함되어 있습니다. 다만 필요에 따라 개발자가 직접 재정의하여 사용할 수 있습니다.
hashCode() 메서드란?
public int hashCode()
이 메서드는 객체의 해시 코드(hash code) 값이라고 불리는 정수(int) 값을 반환합니다. 모든 객체는 생성되는 시점에 고유한 32비트 부호 있는(signed) int 값이 할당되며, 이 값이 바로 해당 객체의 해시 코드 값입니다.
hashCode() 메서드의 일반 계약
- 객체에 저장된 값이 수정되지 않는 한, hashCode() 메서드는 동일한 객체에 대해 몇 번을 호출하더라도 항상 같은 정수 값을 반환해야 합니다.
- equals() 메서드 기준으로 두 객체가 같다면, hashCode() 메서드 역시 두 객체에 대해 반드시 동일한 정수 값을 반환해야 합니다.
- 반면, equals() 기준으로 서로 다른 객체라고 해서 hashCode()가 반드시 서로 다른 값을 반환해야 하는 것은 아닙니다. 즉, 다른 해시 코드를 갖는 것은 권장 사항일 뿐 필수 조건이 아닙니다.
equals() 메서드란?
public boolean equals(Object obj)
Object 클래스의 equals() 메서드는 객체 간의 동등성을 검사하고, 그 결과로 true 또는 false를 반환합니다. Object 클래스가 제공하는 기본 구현은 단순히 두 참조 변수가 힙 메모리상의 동일한 객체를 가리키고 있는지만 확인하며, 객체 내부의 값이나 상태는 비교하지 않습니다. 하지만 이 메서드를 재정의하면 객체의 상태나 값을 기준으로 동등성을 판단하는 자체 로직을 구현할 수 있습니다.
equals() 메서드의 일반 계약
null이 아닌 임의의 참조 변수 a, b, c에 대해 다음 규칙이 성립해야 합니다.
- 반사성(Reflexivity): a.equals(a)는 항상 true를 반환해야 합니다.
- 대칭성(Symmetry): a.equals(b)가 true를 반환하는 것은 b.equals(a)가 true를 반환하는 경우와 정확히 일치해야 합니다.
- 추이성(Transitivity): a.equals(b)가 true이고 b.equals(c)가 true라면, a.equals(c)도 반드시 true를 반환해야 합니다.
- 일관성(Consistency): 객체의 값이 수정되지 않는 한, a.equals(b)를 여러 번 호출했을 때 항상 일관되게 true 또는 일관되게 false를 반환해야 합니다.
- null 처리: a.equals(null)은 항상 false를 반환해야 합니다.
결론적으로, equals() 메서드를 재정의한다면 반드시 Object 클래스의 hashCode() 메서드도 함께 재정의해야 합니다. 그렇지 않으면 HashMap, HashSet과 같은 해시 기반 컬렉션에서 논리적으로 같은 객체임에도 다른 해시 버킷에 저장되어 올바르게 동작하지 않는 문제가 발생할 수 있습니다.
예제 코드
public class MainClass {
public static void main(String[] args) {
MainClass mainClass = new MainClass();
TestClass obj1 = new TestClass(1);
TestClass obj2 = new TestClass(1);
mainClass.test1(obj1, obj2);
TestClass obj3 = new TestClass(1);
TestClass obj4 = new TestClass(2);
mainClass.test2(obj3, obj4);
}
public void test1(TestClass obj1, TestClass obj2) {
if (obj1.equals(obj2)) {
System.out.println("Object One and Object Two are equal");
}
else {
System.out.println("Object One and Object Two are not equal");
}
}
public void test2(TestClass obj3, TestClass obj4) {
if (obj3.equals(obj4)) {
System.out.println("Object Three and Object Four are equal");
}
else {
System.out.println("Object Three and Object Four are not equal");
}
}
}
class TestClass {
private int val;
TestClass(int val) {
this.val = val;
}
public int getValue() {
return val;
}
@Override
public boolean equals(Object o) {
// null 검사
if (o == null) {
return false;
}
// 자기 자신인지 검사
if (this == o) {
return true;
}
// instanceof 검사 및 실제 값 비교
if ((o instanceof TestClass) && (((TestClass) o).getValue() == this.val)) {
return true;
}
else {
return false;
}
}
@Override
public int hashCode() {
int result = 0;
result = (int) (val / 11);
return result;
}
}위 예제에서 TestClass는 equals()를 재정의하여 객체의 val 필드 값이 같으면 동등한 객체로 판단하고, hashCode()도 함께 재정의하여 계약을 준수하고 있습니다.
실행 결과
Object One and Object Two are equal Object Three and Object Four are not equal
값이 1로 동일한 obj1과 obj2는 equals() 비교 결과 같음(true)으로 판정되었고, 값이 1과 2로 서로 다른 obj3과 obj4는 다름(false)으로 판정된 것을 확인할 수 있습니다.