변경할 수 있는 리스트를 만들어야 한다고 가정해 봅시다.
val list1 = mutableListOf<Int>()
var list2 = listOf<Int>()
위 두가지 모두 다음과 같은 방법을 이용하여 변경할 수 있습니다.
list1.add(1)
list2 = list2 + 1
모두 정상적으로 작동하지만 장단점이 있습니다.
두 가지 모두 변경 가능 지점(mutating point)가 있지만 위치가 다르다는 점입니다.
첫 번째 코드는 구체적인 리스트 구현 내부에 변경 가능 지점이 있습니다. 멀티스레드 처리가 이루어지면 내부적으로 동기화가 적절하게 되어 있는지 알 수 없어 위험하게 됩니다.
두 번째 코드는 프로퍼티 자체가 변경 가능 지점입니다. 따라서 멀티스레드 처리의 안정성이 더 좋다고 할 수 있겠지만 구현을 잘못 하면 일부 요소가 손실될 수 있게 됩니다.
mutable 컬렉션을 사용하는 것이 처음에는 더 간단하게 느껴지겠지만, mutable 프로퍼티를 사용하면 객체 변경을 제어하기가 더 쉽습니다.
최악의 방법은 프로퍼티와 컬렉션을 모두 변경 가능한 지점으로 만드는 것입니다.
var list = mutableListOf<Int>()
이렇게 코드를 작성하면, 변경될 수 있는 두 지점 모두에 대한 동기화 구현이 필요하게 됩니다. 또한 모호성이 발생하여 += 를 사용할 수 없게 됩니다.
mutable 프로퍼티에 읽기 전용 컬렉션을 넣어 사용하세요.
var list = listOf<Int>()
private set
이렇게 하면 여러 객체를 변경하는 여러 메서드 대신 세터를 사용하면 되고, 이를 private 로 만들 수도 있기 때문입니다.
그리고 mutable 컬렉션 대신 mutable 프로퍼티를 활용하는 방법 중 사용자 정의 세터 또는 델리게이트를 활용하여 변경을 추적할 수 있습니다. 예를 들어 Delegates.observable을 사용하면, 컬렉션에 변경이 있을 때 로그를 출력할 수 있습니다.
var list by Delegates.observable(listOf<Int>()) { _, old, new ->
println("$old 에서 $new 로 변경되었습니다.")
}
list += 2 // [] 에서 [2] 로 변경되었습니다.
list += 4 // [2] 에서 [2, 4] 로 변경되었습니다.
이 포스팅 내용의 일부를 서적 Effective Kotlin(마르친 모스칼라 지음|윤인성 옮김) 에서 참고하였습니다.