equals와 hashCode에 대해서 알아보자
1. equals
equals() 메서드는 두 객체의 값이 같은지 비교하기 위해 사용하는 Java의 객체 메서드.
기본적으로, Object 클래스에 정의되어 있으며, 모든 Java 클래스는 Object 클래스를 상속받기 때문에 모든 객체에서 사용 가능.
- 기본 구현
public boolean equals(Object obj) {
return (this == obj);
}
- equals() vs == 비교
비교 방식 | 참조(주소) 비교 | 값 비교 (보통 오버라이드 필요) |
기본 구현 | 변경 불가능 | 오버라이드 가능 |
기본 용도 | 객체의 주소(메모리 위치) 비교 | 객체의 값 비교 |
사용 예시 | str1 == str2 | str1.equals(str2) |
- equals() 오버라이드 (커스텀 객체 비교)
직접 만든 클래스를 비교할 때, equals() 메서드를 오버라이드해야 객체의 값을 비교할 수 있다.
Lombok 라이브러리의 @EqualsAndHashCode 어노테이션을 사용하면 간단하게 구현할 수 있지만, 직접 작성하는 방법도 알아둬야 한다.
예시1
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
System.out.println(p1 == p2); // false (주소가 다름)
System.out.println(p1.equals(p2)); // false (기본 Object의 equals()는 주소 비교)
}
}
결과:
- p1 == p2는 false (주소가 다름)
- p1.equals(p2)도 false (기본 Object의 equals()는 주소 비교이기 때문)
예시2
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 같은 객체 참조
if (obj == null || getClass() != obj.getClass()) return false; // 클래스 타입 다르면 false
Person person = (Person) obj;
return age == person.age && name.equals(person.name); // name과 age가 같으면 true
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
System.out.println(p1 == p2); // false (주소가 다름)
System.out.println(p1.equals(p2)); // true (값이 같으므로 true)
}
}
결과:
- p1 == p2는 false (주소가 다름)
- p1.equals(p2)는 true (name과 age가 같기 때문)
2. hashCode
hashCode() 메서드는 객체의 해시 코드를 반환하는 메서드로, 정수(int) 값을 반환한다.
모든 Java 객체는 Object 클래스를 상속하므로, 모든 객체는 hashCode() 메서드를 사용할 수 있다.
이 메서드는 주로 HashMap, HashSet, Hashtable과 같은 해시 기반 컬렉션에서 객체를 빠르게 찾기 위해 사용된다.
- 기본 구현
public native int hashCode();
- 목적
해시 기반 컬렉션의 효율성 향상
- HashSet, HashMap, Hashtable 등에서 객체의 위치를 결정하기 위해 해시코드를 사용한다.
- hashCode()로 해시 버킷(bucket)을 결정한 후, equals()로 중복 확인을 한다.
- hashCode()는 빠르게 위치를 찾기 위해 사용한다.
- equals()는 중복 요소가 있는지 확인하기 위해 사용한다.
3. equals와 hashCode 특징
equals와 hashCode 메서드는 객체의 동등성 비교와 해시값 생성을 위해서 사용할 수 있다.
하지만, 함께 재정의하지 않는다면 예상치 못한 결과를 만들 수 있다.
가령, 해시값을 사용하는 자료구조(HashSet, HashMap..)을 사용할 때 문제가 발생할 수 있다.
예시
class EqualsHashCodeTest {
@Test
@DisplayName("equals만 정의하면 HashSet이 제대로 동작하지 않는다.")
void test() {
// 아래 2개는 같은 구독자
Subscribe subscribe1 = new Subscribe("team.maeilmail@gmail.com", "backend");
Subscribe subscribe2 = new Subscribe("team.maeilmail@gmail.com", "backend");
HashSet<Subscribe> subscribes = new HashSet<>(List.of(subscribe1, subscribe2));
// 결과는 1개여야하는데..? 2개가 나온다.
System.out.println(subscribes.size());
}
class Subscribe {
private final String email;
private final String category;
public Subscribe(String email, String category) {
this.email = email;
this.category = category;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Subscribe subscribe = (Subscribe) o;
return Objects.equals(email, subscribe.email) && Objects.equals(category, subscribe.category);
}
}
}
4. 광고
5. 이런 현상의 이유
해시값을 사용하는 자료구조는 hashCode 메서드의 반환값을 사용하는데, hashCode 메서드의 반환 값이 일치한 이후 equals 메서드의 반환값 참일 때만 논리적으로 같은 객체라고 판단한다.
위 예제에서 Subscribe 클래스는 hashCode 메서드를 재정의하지 않았기 때문에 Object 클래스의 기본 hashCode 메서드를 사용한다. Object 클래스의 기본 hashCode 메서드는 객체의 고유한 주소를 사용하기 때문에 객체마다 다른 값을 반환한다. 따라서 2개의 Subscribe 객체는 다른 객체로 판단되었고 HashSet에서 중복 처리가 되지 않았다.
'개발 > Java & Kotlin' 카테고리의 다른 글
[Spring] Annotation 알아보기 (83) | 2024.12.25 |
---|---|
[Java] 동일성과 동등성 알아보기 (85) | 2024.12.22 |
[JPA] ID 생성 전략 알아보기 (131) | 2024.12.16 |
[Java] 얕은 복사, 깊은 복사 알아보기 (93) | 2024.12.11 |
[Java] Checked Exception, Unchecked Exception 차이 (130) | 2024.12.05 |