본문 바로가기

책리뷰/이펙티브자바

[이펙티브자바] 아이템11. equals를 재정의하려거든 hashCode도 재정의하라

728x90
반응형

equals를 재정의할 때 hashCode를 재정의하지 않으면 HashMap이나 HashSet같은 컬렉션의 원소로 클래스의 인스턴스를 사용할 때 문제가 된다. 

 

Object 명세 규약


  • equals 비교에 사용되는 정보가 바뀌지 않으면 몇 번을 호출해도 hashCode는 같은 값을 리턴한다.
    어플리케이션이 재실행되면 값이 달라질 수도 있다.
  • equals(Object)가 두 객체를 같다고 판단하면 hashCode도 같아야 한다.
  • equals가 다르다고 hashCode도 다른 값을 반환할 필요는 없다.
    하지만 다른 값을 반환해야 해시테이블의 성능이 좋아진다.

@Getter
@Setter
@AllArgsConstructor
public class User {
    private int id;
    private int phone;
    private int age;
}
public static void main(String[] args) {
    HashMap<User, String> map = new HashMap<>();
    map.put(getUser(), "user1");

    String result = map.get(getUser());
    System.out.println(result);
}

private static User getUser() {
    User user = new User(1, 110, 20);
    return user;
}

위의 예제에서 result는 null이 리턴된다.

hashCode를 재정의하지 않았기 때문에 논리적 동치라고 해도 다른 해시코드를 반환한다. 

 

⛔️최악의 hashCode 절대 사용금지

@Override
public int hashCode() {
    return 42;
}

같은 hashCode를 리턴해주면 괜찮을 것 같지만 모두 똑같은 해시값은 모든 객체가 해시테이블 버킷 하나에 담기게 되므로 수행시간이 느려진다. 객체가 많으면 도저히 쓸 수 없다. 

 

좋은 해시 함수는 다른 인스턴스에 다른 해시코드를 반환한다. 

 

 

좋은 hashCode 작성 요령


  • int 변수 result를 선언한 후 아래 방법으로 초기화 한다. 
  • 해당 객체에 핵심 필드에 대해 아래 작업을 수행한다. 
    • 해당 필드의 해시코드를 구한다.
      • 기본 타입 필드라면 Type.haspCode(f) -> Type은 기본타입의 박싱 클래스
      • 참조 타입 필드면 필드의 hashCode를 재귀 호출 -> 필드 값이 null 이면 0
      • 필드가 배열이라면 핵심 원소 각각을 별도 필드로 -> 모든 원소가 핵심 원소면 Arrays.hashCode
    • 필드의 해시코드를 result로 갱신한다.
      • result = 31 * result + c (위에서 구한 해시코드);
      • 곱하는 숫자가 31인 이유는 홀수이며 소수이기 때문 
      • 짝수일 경우 오버플로가 발생하면 정보를 잃을 수 있음 
  • result 를 반환한다. 

hashCode 작성시 주의할 점


  • 파생 필드는 해시 코드 계산에서 제외해도 된다.
  • equals 비교에 사용되지 않는 필드는 반드시 제외한다. 

 

@Override
public int hashCode() {
    int result = Integer.hashCode(id);
    result = 31 * result + Integer.hashCode(phone);
    result = 31 * result + Integer.hashCode(age);
    return result;
}

전형적인 hashCode메서드이다. 

 

hashCode를 구현하는 다른 방법


1️⃣ Objects클래스 

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

Objects클래스는 임의의 개수만큼 객체를 받아서 hashCode를 계산해주는 hash메서드를 제공한다. 

코드는 간결하지만 속도가 느리다. (오토박싱일 경우 더 느려진다.)

 

2️⃣ hashCode 지연 초기화

private int hashCode;

@Override
public int hashCode() {
    int result = hashCode; // default=0
    if( result  == 0) {
        result = Integer.hashCode(id);
        result = 31 * result + Integer.hashCode(phone);
        result = 31 * result + Integer.hashCode(age);
        hashCode = result;
    }
    return result;
}

 hash의 키로 사용 되는 객체가 아니라면 hashCode가 처음 불릴 때 계산하는 lazy initialization을 써도 좋다.

 

 3️⃣ Lombok 사용

@EqualsAndHashCode
public class User {
    private int id;
    private int phone;
    private int age;
    // Exclude는 hashCode생성시 제외
    @EqualsAndHashCode.Exclude private String userName;
}

롬복을 통해서 간편하게 hashCode를 생성할 수 있다.

hashCode 생성시 핵심필드를 제외하면 안되기 때문에 lombok을 사용하면 간편하게 hashCode를 만들 수 있다. 

 

728x90
반응형