var 또는 mutable 객체 사용val을 사용해 읽기 전용 프로퍼티를 만들면 일반적인 방법으로는 값이 변하지 않는다.val list = mutableListOf(1, 2, 3)
val 프로퍼티는 값이 변경될 수 있긴 하지만 프로퍼티 레퍼런스 자체를 변경할 수 없어 동기화 문제를 줄일 수 있다.var 프로퍼티를 사용하는 val 프로퍼티는 var 프로퍼티가 변할 때 변할 수 있다.var name: String = "Marcin"
var surname: String = "Moskaja"
val fullname
get() = "$name $surname"
val는 읽기 전용이지만, 불변을 의미하는 것은 아니다.
val는 정의 옆에 상태가 바로 적히므로 코드 실행을 예측하는 것이 쉽고 스마트 캐스트 등의 추가 기능을 활용할 수 있다.Iterable, Collection, Set, List 인터페이스는 읽기 전용 컬렉션MutableIterable, MutableCollection, MutableSet, MutableList 인터페이스는 읽고 쓸 수 있는 컬렉션mutable이 붙은 인터페이스는 읽기 전용 인터페이스를 상속 받아 변경을 위한 메서드를 추가한 것 뿐이다.Iterable<T>.map과 .filter 등 함수는 ArrayList를 리턴하는데 이는 변경 가능하다.val list = listOf(1, 2, 3)
if (list is MutableList) { // 이렇게 하지 말자
list.add(4)
}
mutable로 변경해야 한다면 list.toMutableList를 활용하면 된다.
Set 또는 Map의 키로 사용할 수 있는데 mutable 객체는 그럴 수 없다.
Set과 Map은 처음 요소 값을 기반으로 버킷을 결정하기 때문에 요소에 수정이 일어나면 내부에서 요소를 찾을 수 없게 되버린다.Int도 내부적으로 plus와 minus 등이 자신을 수정한 새로운 Int를 리턴한다.data 한정자를 사용하면 된다.
copy 메서드를 활용하면 모든 기본 생성자 프로퍼티가 같은 새로운 객체를 만들어 낼 수 있다.data class User(
val name: String,
val surname: String
)
var user = User("Maja", "Markiewicz")
user = user.copy(surname = "Moskaja")
val list1: MutableList<Int> = mutableListOf()
var list2: List<Int> = listOf()
// 두 가지 모두 변경 가능하다.
list1.add(1)
list2 = list2 + 1
list1은 구체적인 리스트 구현 내부에 변경 가능 지점이 있다.
list2는 프로퍼티 자체가 변경 가능 지점이다.
mutable 프로퍼티에 읽기 전용 컬렉션을 사용하는 것이 더 쉽다.
private로 만들 수 있기 때문var announcements = listOf<Announcement>()
private set
mutable 객체를 외부에 노출하는 것은 굉장히 위험하다.
mutable 객체를 복제하는 방어적 복제를 활용하면 좋다.class UserHolder {
private val user: MutableUser()
fun get(): MutableUser = user.copy() // 방어적 복제
}
class UserRepository {
private val storedUsers: MutableMap<Int, String> =
mutableMapOf()
fun loadAll(): Map<Int, String> { // 업캐스팅
return storedUsers
}
}
var 보다는 val를 사용하는 것이 좋다.mutable 프로퍼티/객체보다는 immutable 프로퍼티/객체를 사용하는 것이 좋다.immutable 데이터 클래스로 copy를 활용하는 것이 좋다.mutable 컬렉션보다는 읽기 전용을 사용하는 것이 좋다.mutable 객체를 외부에 노출하지 않는 것이 좋다.