Kotlin에서 shallow copy (얕은 복사)는 새로운 객체를 생성하지만, 중첩된 객체 (예: 컬렉션의 요소 또는 참조 필드)는 복사되지 않고 원본 인스턴스와 동일한 객체를 참조합니다. Deep copy (깊은 복사)는 모든 중첩된 객체 (객체 그래프)를 재귀적으로 복사하여 원본과 복사가 완전히 독립적이도록 합니다.
Kotlin 표준 라이브러리에는 깊은 복사를 위한 일반적인 수단이 없으며, 특히 복잡한 구조나 사이클이 있는 그래프의 경우 수동으로 구현해야 합니다.
data class Person(val name: String, val addresses: List<String>) val original = Person("Anna", listOf("Moscow", "London")) val shallow = original.copy() println(original.addresses === shallow.addresses) // true, 같은 참조
data class Person(val name: String, val addresses: List<String>) { fun deepCopy(): Person = Person(name, addresses.toList()) } val deep = original.deepCopy() printfn(original.addresses === deep.addresses) // false, 리스트의 새로운 복사
만약 중첩된 컬렉션이 있는 data class에 대해
.copy()를 사용하면 컬렉션이 복사됩니까? 왜 그렇습니까?
아니요, copy()는 얕은 복사를 생성합니다. 중첩된 컬렉션과 객체는 동일한 객체에 대한 참조로 남아 있습니다. 이러한 객체의 변경은 원본과 복사 모두에서 표시되며, 이는 버그를 초래할 수 있습니다.
data class Box(val items: MutableList<String>) val box1 = Box(mutableListOf("A")) val box2 = box1.copy() box2.items.add("B") printfn(box1.items) // [A, B] — 원본이 수정되었습니다!
이야기
프로젝트에서 객체 상태를 일반적인
.copy()data class를 통해 저장하면서 "스냅샷"을 얻는다고 믿었습니다. 이전 상태로 돌아갔을 때, 중첩 리스트 (mutableList)가 얕은 복사로 인해 모든 복사본에서 변경되었음을 알게 되었습니다.
이야기
문서 시스템에서 Gson을 사용하여 직렬화/역직렬화로 엔티티를 복사하면서 깊은 복사를 기대했지만 nullable 필드와 날짜가 잘못 직렬화되어 부정확한 값과 비즈니스 로직 오류가 발생했습니다.
이야기
Android 개발을 위한 Redux와 유사한 저장소에서 상태를 마이그레이션할 때, 상태 트리에 대해
copy()를 사용하여 복사본의 완전한 독립성을 기대했습니다. 결과적으로 한 저장소의 브랜치가 동일한 내부 mutable 컬렉션에 대한 참조 때문에 다른 상태를 변경할 수 있었습니다.