본문 바로가기
개발&프로그래밍

[JAVA] equals()와 hashCode() 메서드

by 재아군 2024. 11. 2.

[JAVA] equals()와 hashCode() 메서드

 

 

Java에서 equals()hashCode() 메서드는 객체의 동등성 비교와 해시 기반 컬렉션 사용에 매우 중요한 역할을 다. 이 두 메서드를 제대로 이해하고 구현하지 않으면 예상치 못한 버그가 발생할 수 있다.

이번 글에서는 두 메서드의 올바른 구현 방법과 주의사항에 대해 알아보자.

 

 

equals() 메서드

equals() 메서드의 기본 규칙

equals() 메서드는 다음 다섯 가지 규칙을 만족해야 합니다:

  1. 반사성: x.equals(x)는 항상 true
  2. 대칭성: x.equals(y)가 true이면 y.equals(x)도 true
  3. 추이성: x.equals(y)가 true이고 y.equals(z)가 true이면 x.equals(z)도 true
  4. 일관성: x.equals(y)를 여러 번 호출해도 항상 같은 결과
  5. null 비교: x.equals(null)은 항상 false

 

 

잘못된 구현의 예

public class User {
    private String name;
    private int age;

    // 잘못된 구현 예시
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof User) {
            User other = (User) obj;
            return name.equals(other.name); // age 필드 무시
        }
        return false;
    }

    // hashCode() 미구현
}

 

 

올바른 구현 예시

public class User {
    private String name;
    private int age;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;

        User other = (User) obj;
        return age == other.age && 
               Objects.equals(name, other.name);
    }
}

 

 

hashCode() 메서드

hashCode()의 규칙

  1. 같은 객체의 hashCode()는 여러 번 호출해도 동일한 값 반환
  2. equals()가 true인 두 객체의 hashCode()는 동일
  3. equals()가 false인 두 객체의 hashCode()는 같을 수 있으나, 다른 것이 성능상 유리

 

 

잘못된 구현의 예

public class User {
    private String name;
    private int age;

    @Override
    public int hashCode() {
        return name.hashCode(); // age 필드 무시
    }
}

 

 

올바른 구현 예시

public class User {
    private String name;
    private int age;

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

 

 

equals와 hashCode의 관계

HashSet/HashMap 사용 시 주의사항

public class Example {
    public static void main(String[] args) {
        Set<User> users = new HashSet<>();

        User user1 = new User("John", 30);
        User user2 = new User("John", 30);

        users.add(user1);
        users.add(user2);

        System.out.println(users.size()); // equals/hashCode 구현에 따라 1 또는 2
    }
}

 

실제 문제 사례

// 문제가 될 수 있는 상황
Map<User, String> userMap = new HashMap<>();
User user = new User("John", 30);
userMap.put(user, "some value");

user.setAge(31); // 객체 상태 변경
String value = userMap.get(user); // null이 반환될 수 있음

 

 

올바른 구현 방법

완전한 구현 예시

public class User {
    private final String name;
    private final int age;

    public User(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;

        User other = (User) obj;
        return age == other.age && 
               Objects.equals(name, other.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

 

 

Lombok 활용

@EqualsAndHashCode 사용

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class User {
    private final String name;
    private final int age;

    // 생성자만 구현하면 됨
}

 

특정 필드 제외

@EqualsAndHashCode(exclude = {"temporaryField"})
public class User {
    private final String name;
    private final int age;
    private String temporaryField; // equals/hashCode 계산에서 제외
}

 

 

성능 고려사항

hashCode() 캐싱

public class User {
    private final String name;
    private final int age;
    private int hashCode; // 해시코드 캐싱

    @Override
    public int hashCode() {
        int result = hashCode;
        if (result == 0) {
            result = Objects.hash(name, age);
            hashCode = result;
        }
        return result;
    }
}

 

필드 비교 순서 최적화

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass() != obj.getClass()) return false;

    User other = (User) obj;
    // 비교 비용이 적은 필드부터 비교
    return age == other.age && // 기본 타입 먼저
           Objects.equals(name, other.name); // 객체 타입 나중에
}

 

equals()hashCode()의 올바른 구현은 Java 애플리케이션의 정확성과 성능에 큰 영향을 미친다.

  1. equals()hashCode()는 반드시 함께 구현
  2. 불변 객체를 사용하면 해시 기반 컬렉션에서 더 안전
  3. 가능하면 Lombok의 @EqualsAndHashCode 활용
  4. 성능이 중요한 경우 hashCode 캐싱 고려
  5. IDE의 자동 생성 기능을 활용하되, 생성된 코드 검토 필요

 

댓글