본문 바로가기
안드로이드/Kotlin

[Kotlin] 데이터 클래스(Data Class)

by jinwo_o 2024. 12. 6.
  • Java 에서 클래스는 Object Class 를 상속받고, Object Class 에 정의된 toString(), hashCode(), equals(other: Object) 메서드를 재정의 할 수 있다. Kotlin 에서도 Java 와 마찬가지로 Any Class 를 상속받으며 Any Class 에 정의된 toString(), hashCode, equals 를 재정의 할 수 있다.
    • 이 세가지 메서드를 재정의 하지 않으면 문제가 생기게 된다.
      • equals() 메서드를 재정의 하지 않는 경우, 동등성 연산을 할 때 문제가 생기게 된다.
      • hashCode() 메서드를 재정의 하지 않는 경우, hashMap 혹은 hashSet 과 같은 해시 기반 컬렉션 프레임워크를 사용할 때 문제가 생기게 된다.
      • toString() 메서드는 객체에 대한 정보를 문자열로 출력하는 기능을 제공하지만, 재정의하지 않으면 단순히 인스턴스가 저장된 힙 메모리 위치를 표시하게 된다.
      • 그러나 equals, hashCode, toString 메서드를 재정의하는 것은 번거로운 작업이며, IDE 에서 제공하는 자동 생성 기능을 사용하더라도 보일러플레이트 코드가 생겨 코드가 깔끔하지 않다.
        • 그래서 Kotlin 에서는 이러한 메서드를 자동으로 생성해 주는 data class 를 제공한다.

 

데이터 클래스(Data Class)

  • 컴파일러에서 컴파일 시에 클래스에 공통적으로 필요한 메서드(equals, hashCode, toString)들을 재정의 해주고, 유틸성 메서드(copy)를 만들어주는 클래스
  • 생성자에 1개 이상의 파라미터를 선언해야 한다.
  • 생성자의 파라미터는 val 또는 var 로 선언해야 한다.
  • abstract, open sealed, inner 를 붙일 수 없다.
  • 클래스에 toString(), hashcode(), equals(), copy() 를 override 하면, 그 함수는 직접 구현된 코드를 사용한다.
  • 데이터 클래스는 상속받을 수 없다.

equals(other: Any)

  • 동등성 연산을 위한 메서드
  • equals() 메서드를 재정의 하지 않는 경우, 객체 간의 관계에 대한 *동등성 연산을 할 때 *동일성 연산을 수행한다.
    • 동등성 연산 : 같은 프로퍼티를 갖는 인스턴스인지 비교하는 것. 프로퍼티만 모두 같으면 두 객체는 같다.
    • 동일성 연산 : 두 객체가 동일한 인스턴스인지 비교하는 것. 메모리 주소값이 같은지를 비교한다.
class Client(val name: String, val postalCode: Int)
    
val client1 = Client("오현석", 4122)
val client2 = Client("오현석", 4122)

print(client1 == client2) // false
class Client(val name: String, val postalCode: Int) {
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client)
            return false
        return name == other.name &&
              postalCode == other.postalCode
    }
}

val client1 = Client("오현석", 4122)
val client2 = Client("오현석", 4122)

print(client1 == client2) // true

 

hashCode()

  • hash 와 관련된 연산을 위해 필수적인 hashCode()
  • hash 와 관련된 자료구조(hashMap, hashTable)는 동등성 연산(equals) 전에 먼저 hashCode 에 대한 비교 연산을 수행한다.
    • 즉, hashCode 값이 다르면 동등성 연산이 같더라도 다른 객체로 인식하고, 반대로 hashCode 값이 같으면 동등성 연산이 수행된다.
class Client(val name: String, val postalCode: Int)
    
val processed = hashSetOf(Client("오현석", 4122))
println(processed.contains(Client("오현석", 4122))) // false
class Client(val name: String, val postalCode: Int) {
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client)
            return false
        return name == other.name &&
              postalCode == other.postalCode
    }
    
    override fun hashCode(): Int = name.hashCode() * 31 + postalCode
}

val processed = hashSetOf(Client("오현석", 4122))
println(processed.contains(Client("오현석", 4122))) // true

 

toString()

  • 객체의 상태를 문자열로 출력해주는 메서드
  • toString() 메서드를 재정의 하지 않는 경우, 클래스명과 객체가 저장된 위치를 반환한다.
class Client(val name: String, val postalCode: Int)

val client1 = Client("오현석", 4122)
println(client1) // Client@7291c18f
class Client(val name: String, val postalCode: Int) {
    override fun toString() = "Client(name=$name, postalCode=$postalCode)"
}

val client1 = Client("오현석", 4122)
println(client1) // Client(name=오현석, postalCode=4122)

 

copy()

  • 객체의 새로운 복사본을 만들어 리턴한다.
  • 리턴되는 객체는 얕은 복사로 생성된다.
  • copy() 의 인자로 생성자에 정의된 프로퍼티를 넘길 수 있으며, 그 프로퍼티의 값만 변경되고 나머지 값은 동일한 객체가 생성된다.

 

얕은 복사(Shallow Copy)

  • 객체를 복사할 때, 복사된 객체와 원본 객체가 같은 데이터에 대한 참조를 공유하게 된다.
  • 복사된 객체에서 참조하는 객체의 값을 변경하면, 원본 객체에서 참조하는 객체의 값도 함께 변경된다.
data class CustomData(val list: MutableList<String>)

data class Entity(var id: Int, val customData: CustomData)

val originalEntity = Entity(10, CustomData(mutableListOf("original")))
val copiedEntity = originalEntity.copy()

copiedEntity.id = 20
copiedEntity.customData.list.add("copy")

println(originalEntity) // Entity(id=10, customData=CustomData(list=[original, copy]))
println(copiedEntity) // Entity(id=20, customData=CustomData(list=[original, copy]))

println(originalEntity === copiedEntity) // false
println(originalEntity.customData === copiedEntity.customData) // true

 

깊은 복사(Deep Copy)

  • 객체를 복사할 때, 객체가 참조하는 모든 객체까지 복사한다.
  • 복사된 객체와 원본 객체가 서로 독립적으로 존재하여 복사된 객체에서 참조하는 객체의 값을 변경해도, 원본 객체에서 참조하는 객체의 값은 변경되지 않는다.
class Client(val name: String, val postalCode: Int) {
    
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is Client)
            return false
        return name == other.name &&
              postalCode == other.postalCode
    }

    override fun toString() = "Client(name=$name, postalCode=$postalCode)"
  
    override fun hashCode(): Int = name.hashCode() * 31 + postalCode
  
    fun copy(name: String = this.name, postalCode: Int = this.postalCode) =
      Client(name, postalCode)
}

val lee = Client("이계영", 4122)
val copyLee = lee.copy(postalCode = 4000)

println(lee.hashCode() === copyLee.hashCode()) // false

println(lee) // Client(name=이계영, postalCode=4122)
println(copyLee) // Client(name=이계영, postalCode=4000)

 

데이터 분해 및 대입(Destructuring Declarations)

  • componentN() 메서드 : 객체를 분해하여 해당 객체의 각 속성을 순서대로 반환하는 메서드
    • N : 프로퍼티의 순서 즉, 첫 번째 프로퍼티의 값을 리턴하는 메서드는 component1()으로 정의된다.
val client1 = Client("오현석", 4122)
val (name, postalCode) = client1

print("Client(name=$name, postalCode=$postalCode)") // Client(name=오현석, postalCode=4122)
  • 생성자에 정의된 프로퍼티의 순서대로 변수에 대입한다.

 

Kotlin Class에 정의되어야 하는 equals, hashCode, toString 살펴보기

목적 equals, hashCode, toString 에 대해 안다. 클래스에 정의되어야 하는 메서드 Java에서 클래스는 Object Class를 상속 받고, Object Class에 정의된 toString(), hashCode(), equals(other: Object) 메서드를 재정의 할 수

kotlinworld.com

 

Kotlin data class를 이용하여 equals, hashCode, toString 자동으로 생성하기 + copy 메서드 이용하여 객체 복

목적 Data Class를 이해한다. 개요 Kotlin에서는 모든 클래스는 Any 클래스를 상속 받는다. Any 클래스에는 equals, hashCode, toString 가 정의되어 있고, 따라서 Kotlin의 클래스는 위 메서드들을 재정의하여

kotlinworld.com

 

 

Kotlin In Action 4장

클래스와 인터페이스. 뻔하지 않은 생성자와 프로퍼티. 데이터 클래스. 클래스 위임. object 키워드 사용. 코틀린 인터페이스 안에는 추상 메서드뿐 아니라 구현이 있는 메소드도 정의할 수 있다.

velog.io

 

Kotlin - Data class 이해 및 구현 방법

데이터 클래스(Data class)는 데이터 보관 목적으로 만든 클래스를 말합니다. 데이터 클래스는 프로퍼티에 대한 toString(), hashCode(), equals(), copy() 메소드를 자동으로 만들어 줍니다. 또한 Destructuring De

codechacha.com