프로그래밍Java 개발자

Java에서 equals() 및 hashCode() 메서드는 어떻게 작동하며 어떻게 사용됩니까? 잘못된 구현은 어떤 결과를 초래합니까?

Hintsage AI 어시스턴트로 면접 통과

답변.

Java에서 equals()hashCode() 메서드는 HashMap, HashSet 및 기타와 같은 컬렉션의 올바른 작동에 매우 중요합니다. 이 질문은 종종 초보 개발자들이 간과하지만, 이러한 메서드의 계약을 위반하면 애플리케이션 로직에서 발견하기 어려운 오류가 발생할 수 있습니다.

질문 배경:

Java에서 모든 클래스는 기본적으로 Object 클래스로부터 equals()hashCode() 메서드를 상속받습니다. 기본적으로 equals()는 객체의 참조(즉, 메모리 내 물리적 위치)를 비교하고, hashCode()는 각 객체에 대해 고유한 코드를 반환합니다. 그러나 사용자 정의 클래스에서는 객체의 내용을 비교해야 할 때가 많습니다.

문제:

equals()hashCode() 메서드가 재정의되지 않거나 잘못 재정의되면 해시 기반의 컬렉션에서 객체가 예상치 않게 작동할 수 있습니다. 이로 인해 요소가 누락되거나 중복되거나 검색 오류가 발생할 수 있습니다.

해결책:

항상 두 메서드를 함께 재정의하고 계약을 엄격하게 준수해야 합니다:

  • a.equals(b) == true이면 a.hashCode() == b.hashCode()이어야 합니다.
  • a.equals(b) == false인 경우 hashCode에 대한 요구 사항은 고유성이 필수는 아닙니다.

올바른 구현 예:

public class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } }

주요 특징:

  • equals() 메서드는 반사적, 대칭적, 추이적, 일관성이어야 합니다.
  • hashCode() 메서드는 변경 불가능한 데이터에 대해 객체에 대해 동일한 값을 반환해야 합니다.
  • 재정의된 메서드에서는 반드시 의미 있는 필드를 비교해야 합니다.

함정 질문.

HashSet에 저장될 클래스에서 hashCode() 없이 equals()만 사용할 수 있습니까?

아니요. equals()만 재정의하면 해시 기반 컬렉션이 객체의 고유성을 올바르게 판단하지 못합니다. HashSet은 먼저 hashCode를 비교하고, 그 다음에 equals를 비교합니다.

equals()와 hashCode()에서 클래스의 모든 필드를 사용하는 것이 필수입니까?

아니요. 클래스의 논리적 정체성에 의미 있는 필드만 사용하면 됩니다. 예를 들어, 객체에 내부적으로 고유한 식별자가 있다면 그것만으로 충분합니다.

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

equals()에서 직접 필드 대신 getter에 기반할 수 있습니까?

보통은 가능하지만 부작용이 없고 getter가 안정적일 경우에만 그렇습니다. 그러나 getter가 여러 호출에서 다른 값을 반환할 위험이 있으므로, 그 경우 동작이 예측 불가능해질 수 있습니다.

일반적인 오류 및 안티패턴

  • equals()를 재정의했을 때 hashCode()를 재정의하지 않음.
  • hashCode() 계산에 가변 필드를 사용함.
  • 이들 메서드 간의 계약을 무시함.

실제 사례

부정적 사례

한 개발자가 User 클래스를 구현하고 hashCode()를 잊고 equals()만 정의합니다. 결과적으로 HashSet에 객체를 추가하고 검색할 때 중복이 발생하고 요소가 "사라지는" 문제가 생깁니다.

장점:

  • 최소한의 코드

단점:

  • 컬렉션 작업이 손상됨
  • 애플리케이션에서 불분명하고 잡기 힘든 버그 발생

긍정적 사례

한 개발자가 두 메서드를 계약에 따라 정확하게 구현하고, 동등성과 해시 로직 내에서 id만 사용합니다. 컬렉션은 예상대로 작동하고, 검색 및 저장이 올바르게 이루어집니다.

장점:

  • 예측 가능한 동작
  • 해시 컬렉션의 정확한 작동

단점:

  • 클래스 변경 시 논리를 최신 상태로 유지해야 함