• [EffectiveKotlin] equals, hashcode 규약과 data class, jpa
    언어/Kotlin 2022. 6. 1. 23:50
    반응형

     

     먼저 jdk의 equals, hashcode 함수를 확인해보자. 모든 동등성을 주소값으로 처리한다.

    class Object {
        .
        .
        @IntrinsicCandidate
        public native int hashCode();
        .
        .
        public boolean equals(Object obj) {
            return (this == obj);
        }
        .
        .
    }
    • equals는 객체의 주소값으로 같은지 다른지 판단하고 있다.
      • Kotlin으로 치면 ===

     

    • hashCode는 메모리에서 객체의 Hash 주소값을 반환한다.
      • native 키워드는 함수를 java에서 직접 구현하지 않고, 외부의 함수를 호출할 수 있다.

     

    - 개발 규약

    • hashCode 규약
      • 어떤 객체의 hashCode는 여러 번 호출해도 항상 같은 결과가 나와야 한다.
      • equals의 결과로 두 객체가 같다고 나오면 hashCode의 결과도 같야 한다.

     

    • equals 규약
      • 반사적 : x가 Null이 아닌 값이라면 x.equals(x)는 true이다.
      • 대칭적 : x와 y가 null이 아닌 값이라면, x.equals(y)는 y.equals(x)와 같다.
      • 연속적 : x, y, z가 null이 아닌 값이고 x.equals(y)와 y.equals(z)가 ture라면, x.equals(z)도 true이다.
      • 일관적 : x와 y가 null이 아니라면 x.equals(y)는 여러 번 실행하더라도 항상 같은 결과를 낸다.
      • 널 관련된 동작 : x가 null이 아닌 값이라면, x.equals(null)은 항상 false를 리턴해야 한다.

     

    • hashCode를 override하면 equals도 override 한다.

     

    - 왜 override 하나?

    • Kotlin의 문법과 객체의 사용법을 일관성있게 사용하기 위함이다.
    • List의 contains함수는 equals를 이용하여 동일한 객체를 찾는다. 그러므로 아래와 같은 코드는 개발자의 의도와는 다르게 동작한다.
    class Test(
       val first: String,
       val second: String,
       val third: String,
    )
    
    val testList = listOf(Test("a", "b", "c"), Test("d", "e", "f"))
    
    // Test("a", "b", "c")가 있는지 찾고싶다.
    testList.contains(Test("a", "b", "c")) // false
    • hashCode는 HashMap에서 사용되는 HashTable에서 사용된다. 같은 값을 return할 경우 성능에 영향을 미친다.

     

    - 사용

    1. String의 equals와 hashCode : 사용자 정의 equals와 hashCode

    class String {
       .
       .
       // StringUTF16의hashCode
       public static int hashCode(byte[] value) {
            int h = 0;
            int length = value.length >> 1;
            for (int i = 0; i < length; i++) {
                h = 31 * h + getChar(value, i);
            }
            return h;
        }
        .
        .
        // StringLatin1의 equals
        public static boolean equals(byte[] value, byte[] other) {
            if (value.length == other.length) {
                for (int i = 0; i < value.length; i++) {
                    if (value[i] != other[i]) {
                        return false;
                    }
                }
                return true;
            }
            return false;
        }
        .
        .
    }
    • 우리가 자주 사용하는 String 클래스는 hashCode와 equals를 override하여 사용하기 때문에 아래와 같은 equals(==)사용이 가능하다.
    val text1 = "a"
    val text2 = "a"
    val text3 = String("a".toByteArray())
    
    println(text1 == text2) // true
    println(text1 == text3) // true

    2. 먼저 data class를 고려하자.

    • data class는 컴파일 시점에 equals(), hashCode(), copy(), toString() 함수를 자동으로 생성한다.
    • data class의 equals는 생성자에 정의된 모든 값이 동일한지 아닌지를 체크하기 때문에 일부 값만 비교하고 싶은 경우 equals를 새로 override해야 한다.

     

    3. JPA Entity 객체

    • data class는 사용하지 말자.
    • PK를 활용하여 equals를 구현하자.
    • PK가 null인 객체(영속성 컨텍스트에 의해 관리되지 않는 Entity)는 어떻게 equals를 수행할 것인가?
      • 정답은 없다. 상황에 맞게 구현하자.

     

    - 결론

    • override 전 date class를 먼저 고려하자.
    • hashCode override 시 Obejects.hash함수를 먼저 고려하자.
    • 규약을 지키면서, equals와 hashCode를 override하자

     

    참고 :

    https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Object.java

    https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/String.java

    https://effectivesquid.tistory.com/entry/Kotlin-JPA-%EC%82%AC%EC%9A%A9%EC%8B%9C-Entity-%EC%A0%95%EC%9D%98

    반응형

    댓글

Designed by Tistory.