Kotlin에서는 Java에서 제공하지 않는 MutableCollection이 존재하는데요, Kotlin Collection에서는 Java에서 Collection을 선언한 후에 add, set 등을 통해 컬렉션 값을 주입하는 방식과는 달리 다음과 같이 값 초기화 이후에 변경하는 메서드를 제공하고 있지 않습니다.
public actual interface List<out E> : Collection<E> {
...
}
해당 List는 코틀린에서 변경이 불가능한 Collection 중 하나로, List 제네릭의 out 키워드는 공변성을 나타냅니다.
공변성이 보통 "읽기전용" 으로 쓰임을 이전 글에서 얘기 했다시피, 해당 List는 읽기전용 Collection임을 알 수 있습니다.
이러한 이유 때문에 Kotlin에서는 일반적으로 List<T> 혹은 Map<T>을 선언하면 다음처럼 add 메서드를 제공하지 않습니다.
class Foo{}
val fooList: List<Foo> = listOf()
fooList.add() // 컴파일 에러
Kotlin에서는 이처럼 읽기전용인 Collection을 따로 두고 있는데요, 그렇다면 쓰기 작업은 어떻게 진행하는걸까요?
MutableCollection
Kotlin에서는 이 쓰기 작업을 위해 MutableCollection을 따로 지원하고 있습니다.
MutableCollection은 읽기/쓰기가 가능한 인터페이스로, 다음과 같이 out 키워드가 없는 구현 인터페이스를 제공합니다.
public actual interface MutableList<E> : List<E>, MutableCollection<E> {
...
actual override fun add(element: E): Boolean
actual override fun remove(element: E): Boolean
....
}
해당 인터페이를 보면, 쓰기 작업이 가능한 add 메서드를 확인할 수 있습니다.
Kotlin Collections에는 MutableList 외에도 MutableSet과 MutableMap을 제공하는데요, Kotlin Collection에서 제공하는 클래스 다음 사진의 다이어그램을 통해 어떻게 구성되어 있는지 알 수 있습니다.
Java의 제네릭과는 어떤 차이점이 있을까?
Java의 제네릭에는 단순한 List / Set / Map의 인턴페이스 외에도 TreeSet이나 LinkedList 등 다양한 구현체가 존재하는데요,
코틀린에서 이는 어떻게 표현이 될까요??
먼저, Java의 대표적인 List / Set / Map 컬렉션은 TypeAliases.kt 파일 내에 다음과 같이 정의되어 있습니다.
@SinceKotlin("1.1") public actual typealias ArrayList<E> = java.util.ArrayList<E>
@SinceKotlin("1.1") public actual typealias LinkedHashMap<K, V> = java.util.LinkedHashMap<K, V>
@SinceKotlin("1.1") public actual typealias HashMap<K, V> = java.util.HashMap<K, V>
@SinceKotlin("1.1") public actual typealias LinkedHashSet<E> = java.util.LinkedHashSet<E>
@SinceKotlin("1.1") public actual typealias HashSet<E> = java.util.HashSet<E>
해당 Type들은 Kotlin의 Collection에서 다음과 같이 사용되고 있는 것을 확인할 수 있습니다. 해당 타입들은 Kotlin 인라인 함수로 Java의 제네릭 클래스를 코틀린의 제네릭 타입으로 대치되게끔 설계되어 있는 것을 알 수 있습니다.
List => collections/Collection.kt
@kotlin.internal.InlineOnly
public inline fun <T> listOf(): List<T> = emptyList()
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()
Map => Maps.kt
@kotlin.internal.InlineOnly
public inline fun <K, V> mapOf(): Map<K, V> = emptyMap()
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <K, V> mutableMapOf(): MutableMap<K, V> = LinkedHashMap()
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <K, V> hashMapOf(): HashMap<K, V> = HashMap<K, V>()
Set => Sets.kt
@kotlin.internal.InlineOnly
public inline fun <T> setOf(): Set<T> = emptySet()
public fun <T> mutableSetOf(vararg elements: T): MutableSet<T> = elements.toCollection(LinkedHashSet(mapCapacity(elements.size)))
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> hashSetOf(): HashSet<T> = HashSet()
따라서, ArrayList같은 Java의 Collection이 Kotlin의 상위 Collection인 Iterable 인터페이스로 대치가 되기 때문에 다음과 같은 혼종 코드도 동작할 수 있게 됩니다.
Java의 ArrayList를 Kotlin의 MutableList에 담는다.
val mutableList: List<String> = mutableListOf("hello")
val arrayList = ArrayList<String>()
arrayList.add("bye")
val concatList = mutableList.plus(arrayList)
println(concatList)
// 결과 값 [hello, bye]
Kotlin의 List를 Java의 ArrayList에 담는다.
val arrayList = ArrayList<String>()
arrayList.add("bye")
val emptyList: List<String> = listOf("new Hello")
arrayList.addAll(emptyList)
println(arrayList)
// 결과값 [bye, new Hello]