JAVA

[Java] equals 재정의 할 때 HashCode도 재정의 해야 하는 이유

tudamoa 2025. 7. 16. 00:47

✅ == 연산자

  • 비교 대상참조값(주소) 또는 기본 타입 값 자체
  • 즉, 두 객체가 같은 메모리 주소를 가리키고 있냐를 비교
String a = new String("hello");
String b = new String("hello");

System.out.println(a == b); // false → 주소가 다름

 

→ 두 문자열은 내용이 같아도, new 연산자를 쓰면 서로 다른 객체가 된다. 즉 주소가 다르다.

 

✅ equals() 메서드

  • 비교 대상객체의 내용
  • 즉, 두 객체가 논리적으로 같은 값/의미를 가지고 있냐를 비교
  • String의 equals 메서드는 내용 비교를 수행한다.
String a = new String("hello");
String b = new String("hello");

System.out.println(a.equals(b)); // true → 내용이 같으므로

 


➡️ String 클래스의 equals() 메서드

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        return (anObject instanceof String aString) // anObject이 string인지 검사
                && (!COMPACT_STRINGS || this.coder == aString.coder)
                && StringLatin1.equals(value, aString.value);
                        // equals: byte 배열끼리 비교하는 내부 유틸 메서드
    }

 

💡  하지만 Object의 equals() 메서드는?

public class Object {

    public boolean equals(Object obj) {
        return (this == obj);
    }
}
  • obj와 자신을 비교, 결국 == 와 동일 → 참조 비교

 

✅ '==' 이 있는데, equals()가 필요한 이유

  • 컬렉션에서 동등성 비교할 때 쓰인다. (예: HashSet, HashMap, List의 contains())
  • 데이터의 “논리적 동등성”을 비교하고 싶은 경우
  • 도메인 객체 비교 (ex. 두 회원이 같은 ID를 가지면 같은 사람으로 취급)

 


 

 

✅ Object를 비교할 때 equals() 재정의 안 한 경우

public class User {
    private Long id;
    private String name;

    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class EqualsExample {
    public static void main(String[] args) {
        User user1 = new User(1L, "Alice");
        User user2 = new User(1L, "Alice");

        System.out.println(user1.equals(user2)); // false
    }
}
  • Object의 기본 equals()는 참조(주소)를 비교한다.
  • 그러므로 서로 다른 객체라 생각하여 같은 값이더라도 false가 반환된다.

 

✅ HashSet 일때의 equals() 메서드는?

import java.util.HashSet;
import java.util.Set;

public class EqualsExample {
    public static void main(String[] args) {
        User user1 = new User(1L, "Alice");
        User user2 = new User(1L, "Alice");

        Set<User> users = new HashSet<>();
        users.add(user1);
        users.add(user2);

        System.out.println(users.size()); // 2
    }
}
  • 둘은 값이 같아 보이는데도 HashSet에서 서로 다른 객체로 취급된다.
  • 동일한 값인데 중복으로 들어가서 size가 2가 되는 문제가 발생한다.

이런 이유로 equals()를 값 기준 비교로 재정의해야 한다.

 


 

✅ 2. hashCode를 재정의하지 않았을 때 생기는 문제

➡️ 먼저 equals 재정의

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof User)) return false;
    User user = (User) o;
    return Objects.equals(id, user.id) &&
            Objects.equals(name, user.name);
}

 

이렇게 재정의하면:

User user1 = new User(1L, "Alice");
User user2 = new User(1L, "Alice");

System.out.println(user1.equals(user2)); // true → 내용이 같음

// 재정의 하지 않는다면 ==(주소 비교)를 하므로 false가 출력됨
System.out.println(user1 == user2);      // false → 여전히 다른 객체

 

➡️ HashSet 테스트

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        User user1 = new User(1L, "Alice");
        User user2 = new User(1L, "Alice");

        Set<User> users = new HashSet<>();
        users.add(user1);
        
        System.out.println(user1.equals(user2)); // true 
        System.out.println(users.contains(user2)); // false
    }
}

문제 상황

  • user1.equals(user2) == true 라면 user1과 2는 같은 객체라고 인식할 텐데
  • HashSet에서는 user2가 이미 들어있다고 인식하지 못한다.
    → contains 결과가 false

이유는 HashSet이 내부적으로 이렇게 동작하기 때문이다:

  1. hashCode()로 먼저 후보 그룹을 찾는다.
  2. 후보 안에서 equals()로 최종 비교한다.

equals만 재정의하고 hashCode를 그대로 두면,

  • user1, user2의 hashCode가 달라 서로 다른 그룹에 들어간다.
  • 결국 HashSet에서는 다른 객체라고 인식한다.
public class Main {
    public static void main(String[] args) {
        User user1 = new User(1L, "Alice");
        User user2 = new User(1L, "Alice");
        User user3 = new User(2L, "Bob");

        System.out.println("user1.equals(user2): " + user1.equals(user2)); // true
        System.out.println("user1.equals(user3): " + user1.equals(user3)); // false
        System.out.println("user1.hashCode(): " + user1.hashCode());
        System.out.println("user2.hashCode(): " + user2.hashCode());
        System.out.println("user3.hashCode(): " + user3.hashCode());
    }
}

  • hashcode()를 재정의 하지 않으면,
  • user1과 user2는 같은 이름을 가지고 있으니 같은 hashcode가 나와야 할 것 같지만 다른 값을 출력하게 된다.

 

✅ hashCode()를 재정의 한다면?

@Override
    public int hashCode() {
        return Objects.hash(id, name); // id와 name에 따라 일련의 hash 값 생성
    }

  • 재정의를 하고 코드를 수행하면 user1과 user2가 같은 값이 나오는 것을 확인할 수 있다.

 


 

📚 결론

hashCode()와 equals()의 관계

equals()만 재정의하고 hashCode()를 재정의하지 않으면 두 객체가 논리적으로 같다고 판단되더라도
hashCode()가 다를 수 있다. 이로 인해 HashSet이나 HashMap과 같은 해시 기반 컬렉션에서
문제가 발생할 수 있다. 따라서 equals()와 hashCode()는 일관되게 재정의해야 한다.

 

 


참고자료

https://www.baeldung.com/java-hashcode

 

'JAVA' 카테고리의 다른 글

[JAVA] 객체 지향 프로그래밍의 정의와 특징  (5) 2025.08.13
[JAVA] 예외(Exception) 정리  (1) 2025.06.01
[JAVA] ORM 이란?  (0) 2025.05.31
[JAVA] JDBC 정리  (0) 2025.05.27
[JAVA] 스레드(Thread) 정리  (0) 2025.03.01