ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • kotlin data class 는 상속을 가능하면 쓰지 말자
    개발/삽질 기록 2021. 12. 8. 22:06

     

    kotlin data class는 데이터만 보유하는 클래스를 만들 때 관리하기 용이한 클래스다. data class 로 클래스를 만들면 객체 간의 동등성 비교시 사용되는 hashCode 함수를 자동으로 만들어주기 때문에 단순히 데이터를 관리하는 것 뿐만 아니라 데이터간의 비교도 쉬워진다. 여기서 hashCode 함수는 생성자의 멤버 변수의 값으로 만들어 지는데 이것은 객체 간의 비교를 주소값으로 하지 않고 객체의 멤버변수의 값으로 하기 위함이다.

     

    아래의 User 데이터 클래스로 선언된 a, b 변수는 모두 다른 메모리에서 선언 됐지만 hashCode 상으로는 동일한 값을 보이므로 동등성 비교에서는 같은 값을 보인다. 

     

    data class User(val age: Int, val name: String)
    
    fun main() {
        val a = User(20, "tom")
        val b = User(20, "tom")
        println("a == b: ${a == b} a.hashCode(): ${a.hashCode()}, b.hashCode(): ${b.hashCode()}")
    }
    
    a == b: true a.hashCode(): 115646, b.hashCode(): 115646

     

    하지만 data class로 선언되지 않은 User2 클래스는 내부 멤버 변수의 값이 같더라도 동등성 비교는 같지 않다고 나온다. 

     

    class User2(val age: Int, val name: String)
    
    fun main() {
        val c = User2(20, "tom")
        val d = User2(20, "tom")
        println("c == d: ${c == d} c.hashCode(): ${c.hashCode()}. d.hashCode(): ${d.hashCode()}")
    }
    
    c == d: false c.hashCode(): 1554547125. d.hashCode(): 617901222

     

    그런데 data class 에서 상속을 사용하는 경우 주의가 필요하다.. 값을 변경했어도 data class 내부 멤버 변수가 아니라 부모 클래스의 변수를 변경했을 때는 hashCode 값이 변경되지 않는다. 아래 코드의 경우 User 클래스가 SuperUser 클래스를 상속받고 있다. 변수 a와 달리 변수 b의 경우 생성하면서 gender의 값을 1로 변경했다. 그리고 동등성 비교를 했는데 a, b 모두 동일한 클래스인 것으로 나온다. 

     

    open class SuperUser(var gender: Int = 0)
    data class User(val age: Int, val name: String) : SuperUser(0)
    
    fun main() {
        val a = User(20, "tom")
        val b = User(20, "tom").apply {
            gender = 1
        }
        println("a == b: ${a == b} a.hashCode(): ${a.hashCode()}, b.hashCode(): ${b.hashCode()}")
    }
    
    a == b: true a.hashCode(): 115646, b.hashCode(): 115646

     

    이것은 data class 가 자동으로 생성한 hashCode 함수가 부모 클래스의 변수를 포함하지 않아서 발생하는 문제다. 그래서 data class에서 상속을 사용하는 경우 원래 의도했던 바와 다르게 행동할 수 있는 경우가 존재한다. 그래서 가능하면 상속을 쓰지 않는 것이 좋다. 꼭 써야 한다면 data class가 자동 생성하는 hashCode가 동작할 수 있도록 추상 클래스나 인터페이스를 활용하는 것이 좋다.

     

    abstract class SuperUser { abstract var gender: Int }
    data class User(val age: Int, val name: String, override var gender: Int) : SuperUser()

    댓글

Designed by Tistory.