타입 인자 안에서 ?가 하는 일을 이해하기 위해 파일의 각 줄을 읽어서 숫자로 변환하기 위해 파싱하는 다음 예제를 보자. List<Int>는 Int? 타입의 값을 저장할 수 있다.
fun readNumbers(reader: BufferedReader): List<Int?> {
val result = ArrayList<Int?>()
for (line in reader.lineSequence()) {
try {
val number = line.toInt() // toIntOrNull도 가능
result.add(number)
}
catch (e: NumberFormatException) {
result.add(null)
}
}
return result
}
어떤 변수 타입의 널 가능성과 타입 파라미터로 쓰이는 타입의 널 가능성 사이의 차이는 다음과 같다.
경우에 따라 널이 될 수 있는 값으로 이뤄진 널이 될 수 있는 리스트를 정의해야 할 수도 있다. 코틀린에서는 List<Int?>?로 이를 표현한다.
코틀린 컬렉션과 자바 컬렉션을 나누는 가장 중요한 특성 하나는 코틀린에서는 컬렉션 안의 데이터에 접근하는 인터페이스와 컬렉션 안의 데이터를 변경하는 인터페이스를 분리했다는 점이다. 이런 구분은 코틀린 컬렉션을 다룰 때 사용하는 가장 기초적인 인터페이스인 kotlin.collections.Collection부터 시작한다. Collection 인터페이스를 사용하면 다음을 할 수 있다.
하지만 Collection에는 원소를 추가하거나 제거하는 메소드가 없다.
컬렉션의 데이터를 수정하려면 kotlin.collections.MutableCollection 인터페이스를 사용해야 한다. MutableCollection은 Collection을 확장하면서 원소를 추가하거나, 삭제하거나, 컬렉션 안의 원소를 모두 지우는 등의 메소드를 더 제공한다.
코드에서 가능하면 항상 읽기 전용 인터페이스를 사용하고, 컬렉션을 변경할 필요가 있을 때만 변경 가능한 버전을 사용하자.
어떤 컴포넌트의 내부 상태에 컬렉션이 포함된다면 그 컬렉션을 MutableCollection을 인자로 받는 함수에 전달할 때는 원본의 변경을 막기 위해 컬렉션을 복사해서 전달하는 것이 좋다. 이러 패턴을 방어적 복사(defensive copy)라고 부른다.
fun main() {
val source: Collection<Int> = arrayListOf(2,3,4)
val target : MutableCollection<Int> = arrayListOf(1)
copyElements(source, target)
println(target) // 1, 2, 3, 4
}
fun <T> copyElements(source: Collection<T>, target: MutableCollection<T>) {
for (item in source) {
target.add(item)
}
}
target에 해당하는 인자로 읽기 전용 컬렉션을 넘길 수 없다. 실제 그 값(컬렉션)이 변경 가능한 컬렉션인지 여부와 관계없이 선언된 타입이 읽기 전용이라면 target에 넘기면 컴파일 오류가 난다.
읽기 전용 컬렉션이라고 해서 꼭 변경 불가능한 컬렉션일 필요는 없다. 읽기 전용 인터페이스 타입인변수를 사용할 때 그 인터페이스는 실제로는 어떤 컬렉션 인스턴스를 가리키는 수많은 참조 중 하나일 수 있다.