언어/Kotlin

[EffectiveKotlin] equals, hashcode 규약과 data class, jpa

원석💎-dev 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

반응형