코틀린에서 불변성은 객체가 생성된 후 상태를 변경할 수 없음을 의미합니다. 이는 val (변경 불가능한) 속성을 사용하고, 내부 데이터를 변경할 수 있는 메서드를 가지지 않는 생성자만으로 초기화를 제공하는 클래스를 생성함으로써 달성됩니다.
코틀린은 자바와 달리 data class와 List, Set, Map (기본적으로 변경 불가능한)과 같은 불변 클래스 생성을 위한 편리한 메커니즘을 제공합니다. 그러나 코틀린에서 기본 컬렉션은 인터페이스 수준에서만 불변적이며, 객체가 MutableList 등으로 캐스팅되면 변경될 수 있습니다.
올바른 불변성의 예:
// Data class 및 val은 불변성을 보장합니다. data class User(val name: String, val age: Int) val user = User(name = "Ivan", age = 30) // user.name = "Sergey" // 컴파일 오류
불변성을 위반하는 코드의 예:
class User(var name: String, var age: Int) val user = User("Ivan", 30) user.name = "Sergey" // 객체 변경이 가능합니다.
미세한 사항:
val 속성을 가진 data class도 만약 속성이 참조 형식이고 내부 상태가 변경될 경우 완전한 불변성을 보장하지 않습니다.val + 변경 불가능한 유형을 사용해야 합니다 (예: val scores: List<Int>).
val속성만 가진 data class 객체가 완전히 불변인가요?
답변: 아니오, 속성이 변경 가능한 객체임 (예: val items: MutableList<Int>)이라면 내부 상태를 변경할 수 있습니다.
예시:
data class Group(val members: MutableList<String>) val group = Group(mutableListOf("Tom", "Jerry")) group.members.add("Spike") // 내부 상태가 변경됩니다.
이야기
프로젝트에서
val속성이 있는 불변data class가 사용되었습니다. 한 개발자는 객체를 변경할 수 없다고 생각했지만, 다른 개발자가 컬렉션에 새 요소를 추가했습니다. 이로 인해 데이터의 일관성이 깨지고 두 스레드가 동시에 공유 리스트를 변경할 때 발견하기 어려운 버그가 발생했습니다.
이야기
변경 가능한 컬렉션을 가진 클래스 인스턴스를 애플리케이션의 층 간에 전달할 때 한 층이 내용을 변경하고 다른 층에 알리지 않았습니다. 이로 인해 UI가 발생한 변경 사항을 알지 못하고 잘못된 원시 데이터로 작업하는 상황이 발생했습니다.
이야기
개발자는 코틀린의
List가 항상 불변이라고 잘못 생각했습니다. 실제로는 자바에서 전달된 구현 (ArrayList)이 내부적으로 사용되었고, 목록을 변경하려는 시도가 데이터 손실과 데이터베이스 작업 시의 버그로 이어졌습니다.