- 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' 카테고리의 다른 글
[Kotlin] 함수형 프로그래밍(Functional Programming), 람다(lambda) (0) | 2024.12.06 |
---|---|
[Java] 읽기 전용 컬렉션(List, Set, Map), ArrayList, LinkedList, Array (0) | 2024.12.05 |
[Kotlin] 변수 선언(val, var, const val) (0) | 2024.12.05 |