5장 컬렉션
코틀린은 자바처럼 다수의 객체를 담기 위해 타입을 명시한 컬렉션을 사용한다. 하지만 코틀린은 자바와는 다르게 중개자 역할을 하는 스트림을 거치지 않고 여러 가지 흥미로운 메소드를 컬렉션 클래스에 직접 추가한다.
배열과 컬렉션의 처리 방법, 정렬과 탐색, 읽기 전용 뷰를 제공하는 방법 등 알아보자.
5.1 배열 다루기
이슈: 코틀린에서 배열을 생성하고 배열에 데이터를 추가하고 싶다.
해법: arrayOf 함수를 이용해 배열을 만들고 Array 클래스에 들어 있는 속성과 메소드를 이용해 배열에 들어 있는 값을 다룬다.
설명: 5-1에서 보듯이 자바에서는 new 키워드를 사용해서 배열의 크기를 지정하고 인스턴스화한다.
String[] string = new String[4];
string[0] = "an";
string[1] = "array";
string[2] = "of";
string[3] = "strings";
// 또는 더 쉽게
strings = "an array of strings".split(" ");
코틀린은 배열을 생성하는 arrayOf라는 이름의 간단한 팩도리 메소드를 제공한다. 코틀린에서 배열에 접근할 때 사용하는 문법은 자바와 똑같지만 코틀린에서 Array는 클래스다. 5-2는 arrayOf 팩토리 메소드의 사용법을 보여준다.
val string = arrayOf("this", "is", "an", "array", "of", "strings")
또한 arrayOfNulls 팩토리 메소드를 사용해 널로만 채워진 배열을 생성할 수 있다.
val nullStringArray = arrayOfNuls<String>(5)
배열에 널만 있음에도 불구하고 배열의 특정 타입을 선택해야 한다는 점이 흥미롭다. 해당 배열에 영원히 널만 있진 않을 것이므로 컴파일러는 개발자가 어떤 타입의 레퍼런스를 배열에 추가하려고 하는지를 알아야 한다. emptyArray 팩토리 메소드도 동일한 방법으로 동작한다. Array 클래스에는 public 생성자가 하나만 있다. 이 생성자는 다음의 두 인자를 받는다.
Int 타입의 size init. 즉 (Int) -> T타입의 람다
Array 클래스 생성자의 두번째 인자인 람다는 배열을 생성할 때 인덱스마다 호출된다. 예를 들어 처음 5개의 정수를 제곱한 값의 문자열 배열을 생성하는 코드는 예제 5-4와 같다.
val squares = Array(5) { i -> ( i * i ).toString() } // 1
배열 결과는 {"0", "1", "4", "9", "16"}이다.
Array 클래스에는 squares[1]처럼 대괄호를 사용해 배열의 원소에 접근할 때 호출되는 public 연산자 메소드 get과 set이 정의되어 있다.
코틀린에는 오토박싱과 언박싱 비용을 방지할 수 있는 기본 타입을 나타내는 클래스가 있다. booleanArrayOf, byteArrayOf, shortAr, rayOf, charArrayOf, intArrayOf, longArrayof, flotArrayOf, doubleArrayOf 함수는 예상하는 것처럼 타입 배열을 생성한다.
코틀린에는 명시적인 기본 타입은 없지만 값이 널 허용 값인 경우 생성된 바이트코드는 Integer와 Double 같은 자바 래퍼 클래스를 사용하고, 널 비허용 값인 경우 생성된 바이트코드는 int와 double 같은 기본 타입을 사용한다.
배열의 복장 메소드는 대부분 컬렉션에 있는 이름이 같은 확장 메소드와 동일하게 동작한다. 하지만 배열에는 두어 개의 고유한 확장 함수가 존재한다. 예를 들어 주어진 배열의 적법한 인덱스 값을 알고 싶다면 5-5처럼 Array의 indices 속성을 사용하자.
@Test
fun `valid indices`() {
val strings.indices
assertThat(indices, contains(0, 1, 2, 3, 4, 5))
}
일반적으로 배열을 순회할 때 표준 for-in 루프를 사용하지만 배열의 인덱스 값도 같이 사용하고 싶다면 withIndex 함수를 사용하자.
fun <T> Array<out, T>.withIndex(): Iterable<IndexedValue<T>> data class IndexedValue<out T>(public val index: Int, public val value: T)
보다시피 IndexedValue 클래스는 index와 value 속성을 가진 데이터 클래스다. IndexedValue는 예제 5-6처럼 사용한다.
@Test
fun `withIndex returns IndexValues`() {
val strings = arrayOf("this", "is", "an", "array", "of", "strings")
for((index, value) in string.withIndex()) { // 1
println("index $index maps to $value") // 2
assertTrue(index in 0..5)
}
}
withIndex 호출
각각의 인덱스와 값에 접근
표준 출력에 인쇄한 결과는 다음과 같다.
Index 0 maps to this Index 1 maps to is Index 2 maps to an Index 3 maps to array Index 4 maps to of Index 5 maps to string
대체로 코틀린의 배열은 다은 언어의 배열이 동작하는 방식과 동일하다.
5.2 컬렉션 생성하기
이슈: 리스트, 세트 또는 맵을 생성하고 싶다.
해법: listOf, setOf, mapOf처럼 변경 불가능한 컬렉션을 생성하기 위해 만들어진 함수나 mutableListOf,mutableSetOf, mutableMapOf처럼 변경 가능한 컬렉션을 생성하기 위해 고안된 함수 중 하나를 사용한다.
설명: 어떤 컬렉션의 변경 불가능한 뷰를 얻고 싶다면 kotlin.collections 패키지가 제공하는 유틸리티 함수를 사용할 수 있다. 한 가지 예를 들면 listOf(vararg elements: T): List<T>이 있다. 예제 5-7 listOf 함수의 구현이다.
public fun <T> listOf(vararg element: T): List<T> =
if (elements.size > 0) elements.asLie() else emptyList()
참조된 asList 함수는 명시된 배열을 List로 감싸 돌려주는 Array의 확장 함수다. asList 함수의 결과 리스트는 불변이라고 불리지만 읽기 전용이 좀 더 정확하다고 생각한다. 컬렉션의 원소를 추가하거나 제거할 수 없지만 컬렉션에 있는 객체를 변경할 수 있다면, 해당 리스트는 변경 할 수 있는 것처럼 보일 것이다.
asList의 구현은 읽기 전용 리스트를 리턴하는 자바의 Arrays.asList에 위임한다.
kotlin.collections 패키지에는 비슷한 역할을 하는 다음과 같은 함수가 들어 있다.
listOf
setOf
mapOf
예제 5-8은 리스트와 세트를 생성하는 방법을 보여준다.
var numList = listOf(3, 1, 4, 1, 5, 9) // 1
var numSet = setOf(3, 1, 4, 1, 5, 9) // 2
// numSet.size = 5 // 3
var map = mapOf(1 to "one", 2 to "two", 3 to "three") // 4
불변 리스트 생성
불변 세트 생성
세트는 중복을 포함하지 않음
pair 인스턴스에서 맵 생성
기본적으로 코틀린 컬렉션은 '불변'이다. 그런 의미에서 컬렉션은 원소를 추가하거나 제거하는 메소드를 지원하지 않는다. 컬렉션의 원소 자체를 변경할 수 있다면 컬렉션은 변경 가능한 것처럼 보인다. 하지만 컬렉션 스스로는 오직 읽기 전용 연산만을 지원한다. 컬렉션을 변경하는 메소드는 다음과 같은 팩토리 메소드에서 제공하는 '가변' 인터페이스에 들어 있다.
예제 5-9에서는 앞의 예제와 비슷한 가변 컬렉션 예제를 보여준다.
var numList = mutableListOf(3, 1, 4, 1, 5, 9)
var numSet = mutableSetOf(3, 1, 4, 1, 5, 9)
var map = mutableMapOf(1 to "one", 2 to "two", 3 to "three")
코틀린 표준 라이브러리의 mapOf 함수의 구현이다.
public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K,V> = if (pairs.size > 0) pairs.toMap(LinkedHashMap(mapCapacity(pairs.size))) else emptyMap()
mapOf 함수의 인자는 Pair 인스턴스 타입의 가변 인자 리스트다. 따라서 to 중위 연산자 함수는 map 항목을 생성하는 데 사용된다. 가변 맵 생성을 위해 비슷한 함수가 사용된다.
5-10에서 보듯이 List, Set, Map 인터페이스를 직접 구현한 클래스의 인스턴스도 생성할 수 있다.
@Test
internal fun `instantiating a linked list`() {
val list = LinkedList<Int>()
list.add(3) // 1
list.add(1) // 1
list.addLast(999) // 2
list[2] = 4
list.addAll(listOf(1, 5, 9, 2, 6, 5))
assertThat(list, contains(3, 1, 4, 1, 5, 9, 2, 6, 5))
}
add 메소드는 addList의 별칭이다.
배열 타입 접근은 get 또는 set을 호출한다.
5.3 컬렉션에서 읽기 전용 뷰 생성하기
이슈: 변경 가능한 리스트, 세트, 맵이 있을 때 해당 컬렉션의 읽기 전용 버전을 생성하고 싶다.
해법: toList, toSet, toMap 메소드를 사용해 새로운 읽기 전용 컬렉션을 생성하자. 기존 컬렉션을 바탕으로 읽기 전용 뷰를 만들려면 list, Set 또는 Map 타입의 변수에 기존 컬렉션을 할당한다.
설명: mutableList 팩토리 메소드로 생성 가변 리스트를 살펴보자. 생성된 리스트에는 원하는 대로 리스트를 증가나 감소시킬 수 있는 add, remove 등과 같은 메소드가 있다.
val mutableNums = mutableListOf(3, 1, 4, 1, 5, 9)
변경 가능한 리스트의 읽기 전용 버전을 생성하는 방법은 두 가지다. 첫 번째 방법은 List 타입의 레퍼런스를 리턴하는 toList 메소드를 호출하는 것이다.
@Test fun `toList on mutableList makes a readOnly new list` () { val read }
Last updated
Was this helpful?