NMP-Study / EffectiveJava2022

Effective Java Study 2022
5 stars 0 forks source link

아이템 11. equals를 재정의하려거든 hashCode도 재정의하라 #11

Closed okhee closed 2 years ago

windowforsun commented 2 years ago

Item 11. equals 를 재정의하려거든 hashCode 도 재정의하라.

Object hashcode 명세(equals 와 hashCode 를 꼭 함께 재정의 해야하는 이유)

Object hashCode Reference

hashCode 를 재정의 하지 않았을 때 발생할 수 있는 문제

public class NonOverrideHashCodeTest {
    public static class MyKey {
        private int num;
        private String str;

        public MyKey(int num, String str) {
            this.num = num;
            this.str = str;
        }
    }

    @Test
    public void hashMap() {
        // given
        Map<MyKey, String> map = new HashMap<>();
        MyKey myKey = new MyKey(1, "a");
        map.put(myKey, "myValue1");

        // when
        String actual = map.get(new MyKey(1, "a"));

        // then
        assertThat(actual, is(nullValue()));
    }

    @Test
    public void hashCode_notEqual() {
        // given
        MyKey myKey1 = new MyKey(1, "a");
        MyKey myKey2 = new MyKey(1, "a");

        // when
        int hashCode1 = myKey1.hashCode();
        int hashCode2 = myKey2.hashCode();

        // then
        assertThat(hashCode1, not(hashCode2));
    }
}

올바른 hashCode 재정의 방법

  1. int 형 변수(result)를 하나 선언하고 핵심 필드(equals 에 포함된)의 hashCode 값으로 초기화
  2. hashCode 구하기
    • 기본 타입 필드는 <TypeClass>.hashCode() 사용
    • 참조 타입 필드는 equals 와 동일하게 맞추는것이 중요. 혹은 구현된 hashCode 사용. null 일경우 0사용
    • 배열의 경우 각 원소를 개별 필드로 다뤄야함. 핵심 원소만 사용하거나 Arrays.hasCode() 사용. 배열이 빈경우 0사용
  3. 계산한 해시코드(c) 는 아래 연산으로 result 에 연산 수행
    • result = 31 * result + c
public class MyKey {
    private int i;
    private String str;
    private long l;
    private double d;
    private float f;
    private short s;
    private char c;
    private List<Integer> integers;
    private MySubKey mySubKey;

    @Override
    public int hashCode() {
        int result;
        long temp;

        // int 변수를 핵심 필드중 하나의 해시코드 값으로 초기화
        result = i;
        // String(기본 타입) 해시코드 구하기
        result = 31 * result + (str != null ? str.hashCode() : 0);
        // long 해시코드 구하기
        result = 31 * result + (int) (l ^ (l >>> 32));
        // double 해시코드 구하기
        temp = Double.doubleToLongBits(d);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        // float 해시코드 구하기
        result = 31 * result + (f != +0.0f ? Float.floatToIntBits(f) : 0);
        // short 해시코드 구하기
        result = 31 * result + (int) s;
        // char 해시코드 구하기
        result = 31 * result + (int) c;
        // List 해시코드 구하기
        result = 31 * result + (integers != null ? integers.hashCode() : 0);
        // 참조 타입 해시코드 구하기
        result = 31 * result + (mySubKey != null ? mySubKey.hashCode() : 0);
        return result;
    }
}

간단하게 hashCode 재정의 하기

Java Objects.hash

public class MyKey {
    private int i;
    private String str;
    private long l;
    private double d;
    private float f;
    private short s;
    private char c;
    private List<Integer> integers;
    private MySubKey mySubKey;

    @Override
    public int hashCode() {
        return Objects.hash(i, str, l, d, f, s, c, integers, mySubKey);
    }
}

Guava Objects.hashCode

public class MyKey {
    private int i;
    private String str;
    private long l;
    private double d;
    private float f;
    private short s;
    private char c;
    private List<Integer> integers;
    private MySubKey mySubKey;

    @Override
    public int hashCode() {
        return com.google.common.base.Objects.hashCode(i, str, l, d, f, s, c, integers, mySubKey);
    }
}

common-lang3 HashCodeBuilder

public class MyKey {
    private int i;
    private String str;
    private long l;
    private double d;
    private float f;
    private short s;
    private char c;
    private List<Integer> integers;
    private MySubKey mySubKey;

    @Override
    public int hashCode() {
        return new org.apache.commons.lang3.builder.HashCodeBuilder(17, 37)
              .append(i).append(str).append(l).append(d).append(f).append(s).append(c).append(integers).append(mySubKey).toHashCode();
    }
}

Lombok @EqualsAndHashCode

@EqualsAndHashCode
public class MyKey {
    private int i;
    private String str;
    private long l;
    private double d;
    private float f;
    private short s;
    private char c;
    private List<Integer> integers;
    private MySubKey mySubKey;
}