문제의 역사:
자바에서 컬렉션의 불변성은 Collections.unmodifiableList()와 immutable 클래스를 수동으로 설계하는 방법(final 필드, 세터 없음)을 통해 달성됩니다. 안드로이드 및 서버 개발에서 이는 비용이 많이 들고 불편해졌으며, 스레드 안전성과 데이터의 예측 가능성이 손실되었습니다.
문제:
자바 컬렉션은 기본적으로 안전하지 않습니다. 객체의 참조를 항상 얻고 수정할 수 있습니다. 이는 특히 멀티스레드 환경에서 불변성을 제어하는 것을 복잡하게 하여, 잡기 힘든 버그를 초래합니다.
해결책:
코틀린은 처음부터 read-only와 mutable 컬렉션의 분리를 고려하여 설계되었으며, truly immutable 객체 생성을 위해 val, data class 및 mutable 접두사가 없는 List/Set/Map을 쉽게 사용할 수 있도록 하였습니다. 이 위에 copy() 메소드를 통한 복사 활용이 추가되었습니다.
코드 예:
val nums: List<Int> = listOf(1, 2, 3) // 불변 리스트 // nums.add(4) // 컴파일 오류 val user = User(name = "Alex", age = 30) val updated = user.copy(age = 31) // 불변 map 예 val state: Map<String, Int> = mapOf("a" to 1, "b" to 2)
주요 특징:
val이 중첩 객체의 절대 불변성을 보장합니까?
아니요! val은 변수의 다른 객체를 가리킬 수 없다고 보장할 뿐, 객체의 필드는 변경할 수 있습니다(내부 속성이 var인 경우).
read-only 컬렉션에서 mutable 객체를 얻을 수 있습니까?
네 — 컬렉션이 mutable 객체를 포함하고 있다면, read-only 인터페이스의 컬렉션에도 불구하고 상태를 변경할 수 있습니다.
data class User(var name: String) val users: List<User> = listOf(User("Vasya")) users[0].name = "Petya" // 허용됨
List<Int>를 as?를 통해 MutableList<Int>로 변환하면 어떻게 됩니까?
컴파일되지만, 원래 타입이 read-only 인터페이스였다면 실행 중에 ClassCastException이 발생합니다.
안드로이드 애플리케이션이 state를 val List<User>에 저장하여 불변이라고 생각하는 경우, 한 개발자가 user.name을 수정합니다. 다른 프래그먼트에서 업데이트된 리스트가 예상치 못한 일관성 없는 데이터를 표시하게 되며, 이는 릴리스에서 버그로 나타납니다.
장점:
단점:
오직 val 필드만을 사용한 immutable data class를 사용하고, 내부 객체에 대한 접근을 필터링하며, copy()를 통해 상태를 업데이트합니다.
장점:
단점: