Data Class란?
데이터 보관이 목적인 클래스로, toString(), equals(), hasCode(), copy()가 기본적으로 오버라이딩 및 추가되어 있어 데이터 출력, 비교, 복사가 편하다.
얕은 복사? 깊은 복사?
- 얕은 복사란 "주소 값"을 복사하다는 의미이다.
- 깊은 복사란 "실제 값"을 복사한다는 의미이다.
코틀린에서는 기본적으로 '='을 사용해서 다른 인스턴스를 넣어주면 얕은 복사가 수행된다. 얕은 복사가 수행되게 되면 같은 주소 값을 참조하기 때문에 복사된 객체가 변경되면 원본 객체의 값도 변경된다.
data class Car(private val name: String = "", var position: Int = 0) {
fun move() {
position++
}
}
class DeepCopyTest {
@Test
fun shallowCopyTest() {
val car = Car("car")
val copyCar = car
copyCar.move()
}
}
car 객체를 생성하고 '='을 통해 copyCar에 car를 넣어준다.
copyCar의 position을 변경하였는데 car의 position 역시 변경된 것을 확인할 수 있다.
Data Class의 copy()
그렇다면 본론으로 넘어가서 Data Class의 copy()를 살펴보자.
위와 똑같은 코드에서 '=' 대신 copy() 메서드를 사용해보면
data class Car(private val name: String = "", var position: Int = 0) {
fun move() {
position++
}
}
class DeepCopyTest {
@Test
fun deepCopyTest() {
val car = Car("car")
val copyCar = car.copy()
copyCar.move()
}
}
car와 copyCar의 주소 값이 다르고 원본 car의 position 역시 변하지 않았다.
🤷♂️ 의문점
그렇다면 Data Class의 copy() 메서드는 깊은 복사라고 할 수 있을까?
결론부터 말하자면 "Data Class의 copy()는 얕은 복사"이다.
아래의 예시를 보자
data class Client(val name: String, val pastAddresses: ArrayList<String>)
fun main() {
val client = Client("Alice", arrayListOf("foo", "bar"))
println(client) // 복사 이전 원본 출력
// copy 메서드 수행
val copyClient = client.copy()
copyClient.pastAddresses.add("Blah")
copyClient.pastAddresses.remove("foo")
println(client) // 복사 이후 원본 출력
}
위 코드를 실행해보면
다음과 같은 결과를 확인할 수 있다.
분명 Car의 예시에서는 Data Class의 copy()를 사용하고 나서 원본은 변함이 없었는데 이번 예시에서는 원본의 pastAddresses가 변경되었다. 만약 copy()가 깊은 복사를 수행한다면 원본의 pastAddresses가 그대로 [foo, bar]가 나와야 할 텐데 말이다.
여기서 핵심은 "복사하는 요소의 타입이 기본(Primitive) 타입인지 아닌지"이다.
코틀린이 제공하는 기본(Primitive) 데이터 타입 : Byte, Short, Int, Long, Float, Double, Char, String
Data Class 안에 기본(Primitive) 타입만 있는 경우
data class Client(val name: String, var age: Int)
fun main() {
val client = Client("Alice", 20)
println(client) // 원본 객체 출력
// copy() 메서드 수행
val copyClient = client.copy()
copyClient.age = 22 // 복사 객체 age 변경
println(client) // 원본 객체 출력
println(client.hashCode()) // 원본 객체 해시코드
println(copyClient.hashCode()) // 복사 객체 해시코드
}
- 우리가 흔히 알던 깊은 복사처럼 copyClient.age로 복사본의 나이를 변경하였을 때 원본은 변경되지 않았다.
- 또한 원본 객체의 해시 코드와 복사 객체의 해시 코드가 역시 다른 것을 확인할 수 있다.
Data Class 안에 기본(Primitive) 타입 말고 다른 타입도 존재하는 경우
data class Client(val name: String, val pastAddresses: ArrayList<String>)
fun main() {
val client = Client("Alice", arrayListOf("foo", "bar"))
println(client)
val copyClient = client.copy()
copyClient.pastAddresses.add("Blah")
copyClient.pastAddresses.remove("foo")
println(client)
println(client.hashCode())
println(copyClient.hashCode())
}
- 원본 Client의 pastAddresses 값이 변경되었고, 원본의 해시 코드와 복사본의 해시 코드 역시 동일하다.
- 이렇듯 Data Class 안에 기본(Primitive) 타입 말고 다른 타입도 존재하는 경우, 얕은 복사가 수행된 것을 확인할 수 있다.
결론
코틀린의 Data Class의 copy는 기본(Primitive) 타입 이외의 변수는 얕은 복사가 되기 때문에 얕은 복사로 분류한다.
Ref
https://lengrand.fr/kotlin-data-classes-shallow-copies-and-immutability/
https://stackoverflow.com/questions/57840759/kotlins-copy-in-data-class-does-shallow-copy
https://seosh817.tistory.com/163
'Android' 카테고리의 다른 글
안드로이드 [Kotlin] - 프로가드(Proguard) 설정하기 (0) | 2022.12.26 |
---|---|
안드로이드 [Kotlin] - 핸들러와 루퍼(Handler & Looper) (0) | 2022.12.25 |
안드로이드 [Kotlin] - 안드로이드에서의 싱글톤패턴(Singleton Pattern) with object & DCL (0) | 2022.12.09 |
안드로이드 [Kotlin] - 안드로이드 테스트 자동화 (0) | 2022.11.27 |
안드로이드 [Kotlin] - 프로젝트에 의존성 주입(DI) 적용해보기 - Hilt (0) | 2022.11.10 |