Kotlin in Action: 6.3 컬렉션과 배열
6.3 컬렉션과 배열
6.3.1 널 가능성과 컬렉션
컬렉션을 선언할 때 변수 타입 뒤에 ?를 붙이면 그 변수에 널을 저장할 수 있다는 뜻으로 사용할 수 있다.
널이 될 수 있는 객체를 만들 때 전체를 널이 될 수 있게 만드는지, 아니면 원소만 널이 될 수 있게 만드는지 조심해야 한다.
fun addValidNumbers(numbers: List<Int?>) {
var sumOfValidNumbers = 0
var invalidNumbers = 0
for (number in numbers) {
if (number != null) {
sumOfValidNumbers += number
} else {
invalidNumbers++
}
}
println("Sum of valid numbers: $sumOfValidNumbers")
println("Invalid numbers: $invalidNumbers")
}
위의 코드에서는 리스트의 원소에 접근하면 Int? 타입의 값을 얻는다. 따라서 그 값을 산술식에 사용하기 전에 널 여부를 검사해야 한다.
널이 될 수 있는 값으로 이뤄진 컬렉션으로 널 값을 걸러내는 경우가 자주 있어서 코틀린 표준 라이브러리는 그런 일을 하는 filterNotNull이라는 함수를 제공한다.
fun addValidNumbers(numbers: List<Int?>) {
val validNumbers = numbers.filterNotNull()
println("Sum of valid numbers: ${validNumbers.sum()}")
println("Invalid numbers: ${numbers.size - validNumbers.size}")
}
filterNotNull이 컬렉션 안에 널이 들어있지 않음을 보장해주므로 valiNumbers는 list<Int> 타입이다.
다음은 다른 중요한 기준인 변경 가능한 컬렉션과 읽기 전용 컬렉션에 대해 살펴보자.
6.3.2 읽기 전용과 변경 가능한 컬렉션
코틀린 컬렉션과 자바 컬렉션을 나누는 가장 중요한 특성 하나는 코틀린에서는 컬렉션 안의 데이터에 접근하는 인터페이스와 컬렉션 안의 데이터를 변경하는 인터페이스를 분리했다는 점이다.
이런 구분은 코틀린 컬렉션을 다룰 때 사용하는 가장 기초적인 인터페이스인 kotlin.collections.Collection부터 시작한다.
이 인터페이스에는 원소를 추가하거나 제거하는 메소드가 없다.
컬렉션의 데이터를 수정하려면 MutableCollection을 사용해야 한다. 이 인터페이스는 Collection을 확장하면서 원소를 추가하거나, 삭제하거나, 컬렉션 안의 원소를 모두 지우는 등의 메소드를 더 제공한다.
이렇게 구분하는 이유는 프로그램에서 데이터에 어떤 일이 벌어지는 지를 더 쉽게 이해하기 위함이다.
6.3.3 코틀린 컬렉션과 자바
모든 코틀린 컬렉션은 그에 상응하는 자바 컬렉션 인터페이스의 인스턴스라는 점은 사실이다. 하지만 코틀린은 모든 자바 컬렉션 인터페이스마다 읽기 전용 인터페이스와 변경 가능한 인터페이스라는 두 가지 표현을 제공한다.
이런 성질로 인해 컬렉션의 변경 가능성과 관련해 중요한 문제가 생긴다. 자바는 읽기 전용 컬렉션과 변경 가능 컬렉션을 구분하지 않으므로, 코틀린에서 읽기 전용 Collection으로 선언된 객체라도 자바 코드에서는 그 컬렉션 객체의 내용을 변경할 수 있다.
/* Java */
// CollectionUtils.java
public class CollectionUtils {
public static List<String> uppercaseAll(List<String> items) {
for (int i = 0; i < items.size(); i++) {
items.set(i, items.get(i).toUpperCase());
}
return items;
}
}
// Kotlin
// collections.kt
fun printInUppercase(list: List<String>) {
println(CollectionUtils.uppercaseAll(list))
println(list.first())
}
이제 자바 코드에 선언된 컬렉션을 코틀린에서 어떻게 다루는지 자세히 살펴보자.
6.3.4 컬렉션을 플랫폼 타입으로 다루기
코틀린은 다른 프로그래밍 언어에서 넘어온 타입들을 플랫폼 타입이라고 부른다.
플랫폼 타입의 경우 코틀린 쪽에는 널 관련 정보가 없다. 따라서 컴파일러는 코틀린 코드가 그 타입을 널이 될 수 있는 타입이나 널이 될 수 없는 타입 어느 쪽으로든 사용할 수 있게 허용한다. 마찬가지로 자바 쪽에서 선언한 컬렉션 타입의 변수를 코틀린에서는 플랫폼 타입으로 본다. 플랫폼 타입인 컬렉션은 기본저긍로 변경 가능성에 대해 알 수 없다.
6.3.5 객체의 배열과 원시 타입의 배열
코틀린 배열은 타입 파라미터를 받는 클래스다. 배열의 원소 타입은 바로 그 타입 파라미터에 의해 정해진다. 코틀린에서 배열을 만드는 방법은 다양하다.
-
arrayOf 함수에 원소를 넘기면 배열을 만들 수 있다.
-
arrayOfNulls 함수에 정수 값을 인자로 넘기면 모든 원소가 null이고 인자로 넘긴 값과 크기가 같은 배열을 만들 수 있다. 물론 원소 타입이 널이 될 수 있는 타입인 경우에만 이 함수를 쓸 수 있다.
-
Array 생성자는 배열 크기와 람다를 인자로 받아서 람다를 호출해서 각 배열 원소를 초기화해준다. arryOf를 쓰지 않고 각 원소가 널이 아닌 배열을 만들어야 하는 경우 이 생성자를 사용한다.