
Effective Kotlin을 읽고 정리하는 글이다.
코틀린에서는 val를 사용하면 읽기 전용 프로퍼티를 만들수 있다.
val a = 10
a = 20 // 오류!
하지만 읽기 전용 프로퍼티가 완전히 변경 불가능한 것은 아니라는 것을 주의해야된다.
읽기 전용 프로퍼티가 mutable 객체를 담고 있다면, 내부적으로 변할 수 있다.
val list = mutableListOf(1,2,3)
list.add(4)
print(list) // [1,2,3,4]
또 다른 경우가 있는데 게터를 정의할 때 var 프로퍼티를 사용하면 var 프로퍼티가 변경될 때 변할 수 있다.
var name : String = "Woo"
var SurName : String = "Kim"
val fullName
get() = "$SurName $name"
println(fullName) // "Kim Woo"
name = "Boo"
println(fullName) // "Kim Boo"
위에 설명과 마찬가지로 코틀린에선 읽고 쓸 수 있는 컬렉션과 읽기 전용 컬렉션이 있다.

코틀린을 공부하다가 이런 이미지는 많이 봐왔었다.
mutable이 붙은 인터페이스는 대응되는 읽기 전용 인터페이스를 상속 받아서, 변경을 위한 메서드를 추가한 것이다.
inline fun <T,R> Iterable<T>.map(
transformation: (T) -> R
) : List<R> {
val list = ArrayList<R>()
for (elem in this) {
list.add(transformaiton(elem))
}
return list
}
Iterable.map() 코드를 보면 진짜 불변하게 만들지 않고 읽기 전용으로 설계하였다.
이로 인해서 더 많은 자유를 얻을 수 있다.
가변으로 동작하여 메모리나 접근 자체에 대한 효율을 높이고
사용시에는 읽기 전용으로 안정성을 높일 수 있기 때문이다.
하지만 다운캐스팅을 위반해서 사용하는 일이 있을 수 있다.
val list = listOf(1,2,3)
// 이렇게 사용 금지!!
if(list is MutableList) {
list.add(4)
}
String이나 Int처럼 내부적인 상태를 변경하지 않는 immutable 객체를 많이 사용하면 여러 장점이 있다.
data class User(
val name : String,
val surName : String
)
var user = User("Woo", "Kim")
user = user.copy(surName = "Park")
print(user) // User(name=woo, surName=Park)
이렇게 data 한정자는 copy라는 메서드를 만들어 주기 때문에 데이터 모델 클래스를 만들어 immutable 객체로 만드는 것이 많은 장점을 가지므로 기본적으로 이렇게 사용하는 것이 좋다.
val list1 : MutableList<Int> = mutableListOf()
var list2 : List<Int> = listOf()
list1.add(1)
list2 = list2 + 1
list1과 list2의 동작은 모두 정상적이지만 장단점이 있다.
두가지 모두 변경 가능 지점이 있지만 그 위치가 다르다.
list1은 리스트 구현 내부에 변경 가능 지점이 있고 멀티스레드 처리가 이루어질 경우, 내부적으로 적절한 동기화가 되어 있는지 확실하게 알 수 없다.
list2는 프로퍼티 자체가 변경 가능 지점이다. 따라서 멀티스레드 처리에 안정성이 더 좋다고 할 수 있다.
mutable 컬렉션을 사용하는 것이 더 간단하지만, mutable 프러퍼티를 사용하면 객체 변경을 제어하기 더 쉽다.
// 최악 !!!
var list3 = mutableListOf<Int>()
data class User(val name : String)
class UserRepository {
private val storedUser : MutableMap<Int, String> =
mutableMapOf()
fun loadAll() : MutableMap<Int, String> {
return storedUsers
}
}
상태를 나타내는 mutable 객체를 외부에 노출하는 것은 굉장히 위험하다.
val userRepository = UserRepository()
val storedUsers = userRepository.loadAll()
storedUsers[4] = "Woo"
print(userRepository.loadAll()) // {4=Woo}
이렇게 loadAll()을 사용해서 private 상태인 UserRepository를 외부에서 변경할 수 있게된다.
class UserHolder {
private val user : MutableUser()
fun get() : MutableUser {
return user.copy()
}
}
data class User(val name : String)
class UserRepository {
private val storedUser : MutableMap<Int, String> =
mutableMapOf()
fun loadAll() : Map<Int, String> {
return storedUsers
}
}
loadAll()의 반환값을 MutableMap -> Map으로 업캐스트하여 가변성을 제어할 수 있다.
내가 개발을 하면서 이러한 부분은 고려하지 않은 것 같다.
var보단 val을 선호하며
mutable컬렉션을 선호하기보단 mutable프로퍼티를 선호해야겠다.
끝.