equals와 hashCode
Java에서 `equals()`와 `hashCode()` 메서드는 모든 Java 객체의 부모 객체인 Object 클래스에 정의되어 있다.
때문에 모든 객체는 해당 메서드를 상속 받고 있다. 이는 객체들의 동등성을 비교할 때 사용된다.
`equals()`는 같은 객체인지(메모리 주소가 같은지) 확인하는 기능만 제공한다. (즉 `==`와 동일한 동작을 한다.)
`hashCode()`는 객체를 식별하는 정수값(해시코드)를 반환하는 메서드이다.
🚨 중요한 규칙
`equals()`가 `true`이면 `hashCode()`는 무조건 `true`이다.
하지만 `hashCode()`가 `true`여도 `equals`는 `false`일 수 있다. ⬅ 해시 충돌이 발생할 수 있기 때문.
equals
일반적인 객체에서의 equals 예시
아래는 `equals()`메서드 사용 예시이다.
Person p1 = new Person("Jeny");
Person p2 = new Person("Jeny");
Person p3 = new Person("Lisa");
System.out.println(p1.equals(p2)); // false
System.out.println(p1.equals(p3)); // false
`p1`과 `p2`는 `new Person()`을 사용하여 각각 다른 메모리 공간에 생성된 객체이다.
`equals`는 메모리 주소를 비교하기 때문에 `p1.equals(p2)`는 `false`를 반환한다.
String 객체에서의 equals 예시
String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
❌ 분명 `new`를 사용하여 새로운 객체를 생성하였으므로 s1과 s2는 서로 다른 참조값을 가진다. 때문에 `s1.equals(s2)`는 false가 나와야하지만, 결과는 true이다.
❗️`String`클래스는 기본적으로 `equals()`를 오버라이딩하여 문자열의 내용을 비교하도록 구현되어 있기 때문에, `s1.equals(s2)`가 true를 반환하는 것이다.
equals의 Override
`String`클래스와 같이 객체의 내용으로 비교하도록 구현하고 싶다면 `equals()` 메서드를 오버라이딩해야 한다.
class Person {
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`객체의 name을 비교하고, name이 같은 문자열이면 `true`를 반환하도록 오버라이딩한 것을 확인할 수 있다.
Person p1 = new Person("Jeny");
Person p2 = new Person("Jeny");
Person p3 = new Person("Lisa");
System.out.println(p1.equals(p2)); // true
System.out.println(p1.equals(p3)); // false
때문에 첫번째 예시에서의 결과와 달리, `p1`과 `p2`의 name이 같기 때문에 `p1.equals(p2)`는 `true`를 반환한다.
`p1.equals(p3)`는 name이 서로 다르기 때문에 `true`를 반환한다.
hashCode
hashCode의 필요
`hashCode()`는 `HashSet`, `HashMap`, `HashTable`과 같은 해시 기반 컬렉션에서 주로 사용된다.
이는 빠른 검색과 삽입을 가능하게 하고, 해시값을 기반으로 "버킷"을 찾아 빠르게 접근할 수 있다.
`hashCode()`가 충돌을 최소화하는 방식으로 구현되면, 검색 속도가 O(1)에 가까워진다.
hashCode의 Override
위의 코드를 실행하면 아래와 같은 경고가 뜰 수 있다.
Warning: Overriding 'equals' without overriding 'hashCode'
말 그대로, `hashCode()`는 오버라이딩 하지 않고 `equals()`만 오버라이딩 했다는 경고 문구이다.
실제로, Java의 Object 문서에는 🚨`equals()`를 오버라이딩하면 `hashCode()`도 함께 오버라이딩 해야한다고 명시하고 있다.
이는 컬렉션(`HashSet`, `HashMap`, `HashTable`)에서 객체를 비교할 때 `hashCode()`가 사용되기 때문이다.
아래는 `Person`객체에서 `equals()` 메서드만 오버라이딩 되었을 때 `HashSet`을 사용하는 예시이다.
Person p1 = new Person("Jeny");
Person p2 = new Person("Jeny");
HashSet<Person> set = new HashSet<>();
set.add(p1);
System.out.println(p1.equals(p2)); // true
System.out.println(set.contains(p2)); // false
❌ p1과 p2는 같은 name을 가지고 있으므로 논리적으로 같은 객체이다(`p1.equals(p2) == true`). 하지만 `set`에 `p1`을 추가하고 `p2` 포함 여부를 살피면 `false`가 반환된다.
❗️이는 `hashCode()`를 오버라이딩 하지 않아 서로 다른 값이 반환되어 `HashSet`이 같은 객체로 인식하지 않기 때문이다.
이를 해결하기 위해 Person 객체의 hashCode()메서드를 오버라이딩한다.
import java.util.Objects;
class Person {
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 Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
Person p1 = new Person("Jeny");
Person p2 = new Person("Jeny");
HashSet<Person> set = new HashSet<>();
set.add(p1);
System.out.println(p1.equals(p2)); // true
System.out.println(set.contains(p2)); // true
다시 `set`에 `p1`을 추가하고 `p2` 포함 여부를 살피면 `true`가 반환된다.
`hashCode()`가 `name`의 `hash`를 반환하도록 오버라이딩 했기 때문에, `name`이 같으면 논리적으로 같은 객체라고 판단한다.
마무리
`equals()`는 두 객체의 논리적 동등성을 비교하는데 사용된다. 내용 기반으로 객체를 비교할 수 있도록 해준다.
`hashCode()`는 해시 기반 컬렉션에서 성능을 최적화하는 데 중요한 역할을 한다. 효율적인 검색과 빠른 데이터 처리를 가능하게 해준다.
실제 프로젝트에서 equals()와 hashCode()를 제대로 오버라이딩하지 않으면, 해시 기반 컬렉션(HashSet, HashMap, HashTable 등)을 사용할 때 예상치 못한 버그나 성능 저하가 발생할 수 있다. 예를 들어, 중복된 객체가 컬렉션에 추가되거나, 객체가 제대로 검색되지 않는 문제가 발생할 수 있다.
'Language > Java' 카테고리의 다른 글
Java 기술 면접 대비 (0) | 2025.03.20 |
---|---|
[JSP] JSP(JavaServer Pages)의 정의와 기본 구성 요소 (1) | 2025.01.02 |
record 클래스 (Java 14) (0) | 2024.12.13 |
직렬화(Serialization) (0) | 2024.12.12 |
동기화(synchronization)와 락(Lock) (1) | 2024.12.11 |