본문 바로가기
Android & Kotlin/Kotlin

[Kotlin] data class vs class (toString, equals, hashCode, copy)

by 말린밴댕이_공부 2024. 5. 4.
반응형

자바 플랫폼에서는 클래스가 equals, hashCode, toString등의 메서드를 구현해야 합니다.

코틀린에서는 이런 매서드를 기계적으로 생성하는 작업을 보이지 않는 곳에서 해주게 된다.

따라서 필수 메서드로 인한 더러움(?) 없이 소스코드를 깔끔하게 유지할 수 있다.

 

자동 메서드를 구현 → 보일러 플레이트 감소!

 

문자열 표현 : toString()

기본 제공되는 문자열 표현은 Client@aejvn123 같은 방식에서 기본 구현을 바꾸려면 toString 메서드를 오버라이드 해아하며 다음과 같이 구현된다.

class Client(val name : String, val code : Int){
    override fun toString(): String {
        return "Clent(name = $name, code = $code)"
    }
}

 

객체의 동등성 : equals()

 

서로 다른 두 객체가 내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 간주해야 할 수 있다.

    val client1 = Client("밴댕이",1234)
    val client2 = Client("밴댕이",1234)
    println(client1 == client2)

 

위의 Client class 를 바탕으로 초기화한 객체의 비교 결과는 false이다.

두 객체의 동등성을 검사하려면 equals를 오버라이드 할 필요가 있다는 의미가 된다.

 

동등성 동일성 이란?

동등성( ‘==’ )

  • 두 객체가 동등함을 비교하며 즉, 값이 같은지 비교
  • 동일한 값을 비교 → equals는 동등성 비교

동일성( ‘===’)

  • 두 객체가 동일하다는 것을 비교함. 즉, 메모리 상의 같은 위치를 참조하는지 비교
  • 메모리 주소 비교 판단

예시

val a: String = "test"
val b: String = "test"
val c: String = a

println(a == b) // true (동등성)
println(a === b) // false (동일성)
println(a === c) // true (동일성)

다음과 같은 결과가 나오게 된다는 것이다.

 

 

이제 동등성에 대한 이해와 equals를 추가한 Client 클래스를 살펴보자

class Client(val name : String, val code : Int){
    override fun toString(): String {
        return "Clent(name = $name, code = $code)"
    }

    override fun equals(other: Any?): Boolean {
        if(other == null || other !is Client)
            return false
        return name == other.name && code == other.code
    }
}

fun main(){
    val client1 = Client("밴댕이",1234) 
    val client2 = Client("밴댕이",1234)
    println(client1 == client2) // true 결과 반환
}

프로퍼티의 값이 모두 동일하니 두 객체는 동등하다고 볼 수 있다.

하지만 Client Class가 더욱 복잡한 작업을 수행하다보면 제대로 동작하지 않는 경우가 존재한다.

이와 관련해 hashCode 없다는 점이 원인이다.

 

hash Container : hasCode()

equals를 오버라이드 할 때 반드시 hashCode를 오버라이드 해야하며 이유를 살펴보자

fun main(){
    val hashSet = hashSetOf(Client("밴댕이",1234))
    println(hashSet.contains(Client("밴댕이",1234)))
}

 

다음의 결과를 true라고 예상할 수 있지만 실제로는 false가 나오게 된다.

이 이유는 hashCode메서드를 정의하지 않았기 때문이다.

 

왜?

 

JVM언어에서는 equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 하는 제약이 있는데 Client는 이를 어기고 hashCode 메서드가 존재하지 않는다.

HashSet같읜 경우 객체의 해시코드를 비교하고 해시코드가 같은 경우에만 실제로 값을 비교한다

(해시코드 → 실제 값 비교 순서)

 

따라서 다음과 같이 세가지를 오버라이드 해야한다.

class Client(val name : String, val code : Int){
    override fun toString(): String {
        return "Clent(name = $name, code = $code)"
    }

    override fun equals(other: Any?): Boolean {
        if(other == null || other !is Client)
            return false
        return name == other.name && code == other.code
    }

    override fun hashCode(): Int {
        return name.hashCode() * 31 + code
    }
}

 

데이터 클래스 Data Class : 클래스가 정의해야하는 메서드 자동 생성

 

앞서 맨 위에서 말했듯이 DataClass는 기계적으로 생성을 해준다는 것을 말을 했다.

data라는 변경자를 앞에 붙이면 필요한 메서드를 컴파일러가 자동으로 만들어준다!

 

위의 class 를 아래와 같이 data 변경자를 통해 쉽게 정의할 수 있다.

data class Client(val name : String, val code : Int)

 

주의할 점은 여기서 equals와 hashCode는 주 생성자에 나열된 모든 프로퍼티만을 고려해 만들어지며 주 생성자 밖에서 정의한 프로퍼티는 고려의 대상이 아니다. (당연한 말이긴 함)

 

데이터 클래스와 불변성 : copy()

 

데이터 클래스의 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변 클래스로 만들어야 한다는 얘기를 많이 들었을 것이다.

 

이를 바탕으로 데이터 클래스 인스턴스를 불변 객체로 더 쉽게 활용할 수 있게 코틀린 컴파일러는 한가지 메서드를 제공해준다.

 

객체를 복사하면서 일부 프로퍼티를 바꿀수 있는 Copy 메서드 이다.

class Client(val name : String, val code : Int){

		//세가지 메서드 toString, equals, hashCode

    fun copy(name : String = this.name, code : Int = this.code) : Client{
        return Client(name, code)
    }
}

fun main(){
    val client1 = Client("밴댕이",1234)
    println(client1.copy(code = 4321))
}

//Clent(name = 밴댕이, code = 4321)

data class를 통해 이렇게 네가지 메서드를 관찰해보았다.

 

data class 정리 (Data Class vs class)

: 데이터 저장과 전달을 위해 사용되며 객체의 상태와 동작을 표현하는 클래스와 성격이 다르다.

  • 네가지 메서드를 자동으로 구현해 주어 보일러 플레이트를 줄여줌
  • 생성자는 한개 이상의 프로퍼티를 가져야 함
  • 불변 객체로 사용되며 속성 값 변경을 할 수 없게
반응형

댓글