[Java] 왜 equals 와 hashCode 를 함께 오버라이딩해야 할까 ?
🎯 equals란 ?
equals() 란 Java 에서 두 객체가 동일한지를 비교하는 메서드 입니다. 기본적으로 Object 클래스에서 제공하며, 기본 구현은 두 객체의 참조값(메모리 주소)를 비교합니다. 하지만 우리가 의미적으로 동일한지 비교하려면 equals() 메서드를 재정의 해야 합니다.
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equals(s2)); // true, 값이 동일하기 때문
System.out.println(s1 == s2); // false, 서로 다른 객체 참조
커스터마이징 예제
class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return name.equals(person.name);
}
}
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
System.out.println(p1.equals(p2)); // true, 논리적으로 동일
즉, 이와 같이 equals() 를 재정의 한다면, 메모리 주소 값이 아닌 객체의 필드값으로 객체의 동일성을 판별할 수 있습니다.
🎯 hashCode()란?
hashCode() 는 객체를 해시 기반 컬렉션에 저장하거나 검색할때 사용되는 교유한 해시 코드를 반환합니다. 해시 코드는 객체의 메모리를 기반으로 만들어지며, 이러한 특징때문에 객체의 지문이라고 불립니다.
String s1 = "hello";
String s2 = "hello";
System.out.println(s1.hashCode()); // 99162322
System.out.println(s2.hashCode()); // 99162322, 동일한 값
🎯 함께 오버라이딩 해야 하는 이유
equals() 와 hashCode() 는 해시 기반 컬렉션의 작동 원리와 관련이 있습니다. 다음 예시를 보며 이해해보겠습니다.
equals() 만 재정의한 경우
public class Car {
private final String name;
public Car(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Objects.equals(name, car.name);
}
}
현재 Car 클래스는 equals() 만 재정의했습니다.
public static void main(String[] args) {
Set<Car> cars = new HashSet<>();
cars.add(new Car("foo"));
cars.add(new Car("foo"));
System.out.println(cars.size()); // 2 출력
}
Set 는 중복되지 않은 Car 를 관리하는 자료 구조 입니다. 우리는 지금 Car 객체가 서로 같은 객체임을 나타냈지만 실제 결과를 확인하면 Set 의 사이즈가 2 로 출력되는 것을 확인할 수 있습니다. 이처럼 hashCode 를 equals() 와 함께 재정의 하지 않으면 코드과 예상과 다르게 작동하는 문제가 발생합니다.
정리하면 hash 를 사용하는 Collection(HashSet, HashMap, HashTable) 등의 자료구조를 사용할때 문제가 발생합니다. 이러한 문제가 발생하는 이유는 다음과 같습니다.
hash 를 사용하는 Collection 은 객체가 같은지 비교할때 아래와 같은 과정을 거칩니다. hashCode 메서드의 리턴값이 우선 일치하고 equals 메서드의 리턴값이 true 임이 확인 되어야 같은 객체라고 판단합니다.
하지만 위 예시에서는 Car 클래스는 hashCode 를 재정의하지 않아, Object 클래스에서 제공하는 hashCode 가 사용되었습니다. Object 에서 제공하는 hashCode 는 객체의 고유한 주소값을 int 값으로 변환하기 때문에 객체마다 다른 값을 리턴합니다. 즉, equals() 로 객체를 비교하기 전에 서로 다른 객체임으로 판별한 것 입니다.
이러한 이유때문에 우리는 객체의 동일성을 비교할때 equals() 메서드를 오버라이딩할때 hashCode() 도 함께 오버라이딩 해야 합니다.