Post

(Kotlin) 컬렉션과 배열

filterNotNull() : 널이 아닌 값만 추리고 싶은 경우
1
2
3
4
>>> val l = listOf(1, 2, null, 4, 5)
>>> println(l.filterNotNull())
[1, 2, 4, 5]

Note ) 걸러내고 난 결과 컬렉션의 타입은 null이 제거되었으므로 List<Int> 타입이 된다.

filterIsInstance() : 특정 타입만 추리고 싶은 경우
1
2
3
4
>>> val li = listOf(1, "b", 3)
>>> li.filterIsInstance<Int>()
[1, 3]

toTypedArray() : 배열을 넘겨야 하는데 데이터가 컬렉션에 들어있다면 배열로 변환
1
2
3
4
>>> val strings = listOf("a", "b", "c")
>>> println("%s/%s/%s".format(\*strings.toTypedArray()))
a/b/c

toIntArray() : 박싱하지 않은 원시 타입 배열로 변환
1
2
3
val l = listOf(1, 2, 3)
l.toIntArray()

배열 n개 잡기
1
2
3
4
>>> val foo = List<Int>(3) {0}
>>> foo
[0, 0, 0]

빈 리스트 생성
1
2
3
4
5
val l = listOf<T>()
val l = emptyList<T>()
val l: List<Int> = ArrayList<Int>()    // 위 두개는 내부적으로 이렇게 처리되는거라, 직접 이렇게 써줘도 되기는 한다
// 다만 아래에 기술해 놓았 듯이 더 추상화 된 쪽을 쓰는게 좋으니까, 위에 있는 것을 사용하도록 하자.

컬렉션

코틀린 컬렉션은 자바 컬렉션과 동일한 클래스지만, 확장 함수를 이용해 더 많은 기능을 제공한다.

읽기 전용 컬렉션과 변경 가능 컬렉션

자바에서 컬렉션은 크게 List / Set / Map으로 구분할 수 있으며 코틀린에서 이를 사용하려면 다음 함수를 이용한다.

1
2
3
4
5
6
readonly      mutable
--------      -------
listOf        mutableListOf, arrayListOf
setOf         mutableSetOf, hashSetOf, linkedSetOf, sortedSetOf
mapOf         mutableMapOf, hashMapOf, linkedMapOf, sortedMapOf

  • 일반적으로 왼쪽에 있는 함수일 수록 사용을 권장하는 편이다.
  • 예를 들어 MutableList / ArrayList둘 다 내부적으로는 kt ArrayList로 컴파일 되기는 한다.
  • 전자는 아무 “가변 리스트”를 의미하므로 추후 코틀린 자료구조가 수정되면 더 좋은 자료구조로 변경될 수 있다. (조금 더 추상화 되어 있다.)
  • 반면 ArrayList는 여기서는 딱 이 자료구조가 적합할 때 사용하는게 좋다.

List<E>자체는 interface지만 List<E>(3) {0}과 같이 선언해서 사용할 수 있는데, 다음과 같이 인라인 되어 있기 때문이다. 최종적으로 반환되는건 ArrayList 객체지만, 타입은 List다. 그리고 List 계열이므로 read-only가 맞다.

1
2
3
4
5
6
7
8
9
10
11
12
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> List(size: Int, init: (index: Int) -> T): List<T> = MutableList(size, init)

  

public inline fun <T> MutableList(size: Int, init: (index: Int) -> T): MutableList<T> {
val list = ArrayList<T>(size)
repeat(size) { index -> list.add(init(index)) }
return list
}

  • 코틀린 컬렉션은 읽기 전용 컬렉션 인터페이스 kotlin.collections.Collection과 이를 상속해 기능을 확장한 변경 가능 컬렉션 인터페이스 kotlin.collections.MutableCollection으로 나뉜다.
  • 웬만하면 val를 사용하는 원칙과 같은 맥락에서, 웬만하면 읽기 전용 컬렉션을 사용한다.
  • Note) 읽기 전용 컬렉션이 곧 불변(Immutable) 컬렉션을 의미하는 것은 아니다. 어떤 컬렉션에 대해 나는 읽기 전용 인터페이스로 접근 했는데, 다른 곳(또는 다른 스레드)에서는 변경 가능 인터페이스로 접근해 값을 변경한다면 컬렉션이 변경될 수 있다.
  • 따라서읽기 전용 컬렉션이라고 thread safe하지는 않기 때문에 멀티스레드인 경우 동기화 문제를 고려해야 한다.

Note ) 컬렉션에 연산자 오버로딩은 웬만하면 사용하지 않는 것이 좋다. 연산자 오버로딩을 사용하면 아예 새로운 컬렉션을 만들어서 그 컬렉션을 가리키도록 변경되기 때문에, 읽기 전용 컬렉션을 var로 선언한 경우 컬렉션이 변경되는 것 처럼 보일 수 있다.

1
2
3
4
5
6
7
8
9
>>> var l = listOf(1, 2, 3)    // val이면 연산자 오버로딩에서 에러 발생
>>> l += 4    // 이 때 아예 새로운 List가 반환된다.
>>> l.map({ println(it) })
1234
>>> val ml = mutableListOf<Int>(1, 2, 3)
>>> ml.add(4)
>>> ml.map({ println(it) })
1234

플랫폼 타입

자바에서는 읽기 전용 컬렉션을 따로 구분하지 않으므로 코틀린에서 읽기 전용이라도 자바에서는 그 컬렉션을 변경할 수 있다. 주의해야 한다. * 같은 이슈가 non-null 타입에도 발생한다. 널이 될 수 없는 원소로 이루어진 컬렉션을 자바로 넘겼을 때, 자바에서 컬렉션에 null을 넣어버릴 수도 있다.

또한 자바 쪽에서 선언한 컬렉션은 변경 가능성에 대해 알 수 없는 플랫폼 타입이므로 읽기 전용이나 변경 가능 어느 쪽으로든 다룰 수 있다. 보통 다음 세 가지 정도를 고려하면 된다.

  • 컬렉션 자체의 널 가능성
  • 컬렉션 원소의 널 가능성
  • 오버라이드 하는 메소드가 컬렉션을 변경할 수 있는지 여부
원소가 널 가능? 컬렉션 자체가 널 가능?

리스트 자체가 널 가능이면서 내부의 원소 Int도 널이 될 수 있는 타입은 다음과 같다.

1
2
List<Int?>?

배열 - 컬렉션까지는 필요 없을 때.

배열을 사용해야 하는 경우는 주로 두 가지 정도다.

  • 배열을 인자로 받는 자바 함수를 호출하는 경우
  • vararg 파라미터를 받는 코틀린 함수를 호출하는 경우
박싱 타입 배열
1
2
3
4
5
6
7
8
9
10
>>> val a1 = arrayOf(1, 2, 3)
>>> a1.map({ println(it) })
123
>>> val a2 = arrayOfNulls<Int>(3)
>>> a2.map({ println(it) })
nullnullnull
>>> val a3 = Array<Int>(3) { i -> i+1 }
>>> a3.map({ println(it) })
123

Array<Int>는 제네릭 클래스처럼 보이지만 자바 배열로 컴파일된다. 원시 타입이 아니라 박싱 타입 배열로. 코틀린에서는컬렉션에 사용할 수 있는 모든 확장 함수를 배열에도 제공 한다. 단, 이런 함수가 반환하는 값은 배열이 아니라 리스트다.

원시 타입 배열

박싱하지 않은, 원시 타입 배열이 필요다하면 다음을 사용해야 한다.

1
2
3
4
5
6
7
8
9
10
// java int[] 대응
val primitive = IntArray(3)    // 0으로 초기화됨.
val primitive2 = intArrayOf(0, 0, 0)
val primitive3 = IntArray(3) { i -> i+1 }

  

CharArray()
ByteArray()

배열 순회 ( 반복 )

인덱스가 필요 없다면, map()을 사용하는 방법, .forEach { }를 사용하는 방법. 인덱스가 필요하다면 .withIndex()를 사용하는 방법, .indices를 사용하는 방법, .forEachIndexed { }를 사용하는 방법이 있다.

1
2
3
4
5
6
fun main(args: Array<String>) {
args.forEachIndexed { i, s ->
println("$i : $s")
}
}

This post is licensed under CC BY 4.0 by the author.