•
Kotlin 표준 라이브러리는 문제 해결에 중요하고 일반적으로 많이 다뤄지는 컬렉션을 관리하기 위한 포괄적인 도구들을 제공한다.
•
Kotlin의 컬렉션은 Java 또는 Python 과 같은 대부분의 프로그래밍 언어의 컬렉션과 비슷한 일반적인 개념이다.
컬렉션
•
컬렉션은 일반적으로 같은 타입(또는 하위 타입)의 여러 객체를 포함한다.
•
컬렉션 내 객체는 요소 또는 항목이라고한다.
•
Kotlin은 저장된 객체의 타입에 상관없이 동일하게 컬렉션을 조작할 수 있게 해준다.
•
Kotlin 표준 라이브러리는 모든 타입의 컬렉션을 생성, 채우기, 관리할 수 있는 제네릭 인터페이스, 클래스 및 함수를 제공한다.
•
컬렉션 인터페이스 및 관련 함수들은 kotlin.collections 패키지에 위치한다.
배열은 컬렉션 타입이 아니다.
컬렉션 타입
•
컬렉션 타입은 두 개의 인터페이스로 나뉜다.
◦
컬렉션 요소에 접근하는 작업을 제공하는 읽기 전용 인터페이스
◦
읽기 전용 인터페이스를 확장하여 요소 추가, 삭제, 업데이트와 같은 쓰기 작업을 제공하는 가변 인터페이스
val numbers = mutableListOf("one", "two", "three", "four")
numbers.add("five") // 가능
println(numbers)
//numbers = mutableListOf("six", "seven") // 컴파일 오류
Kotlin
복사
•
가변 컬렉션이 반드시 var로 할당될 필요는 없고 val로 할당되더라도 쓰기 작업은 가능하다.
•
가변 컬렉션을 val로 할당하는 이점은 컬렉션 참조가 수정되지 않도록 보호할 수 있다는 점이다.
•
코드가 복잡해짐에 따라 참조의 비의도적인 수정으로부터 보호하는 것이 중요해진다.
•
가능한 한 val을 사용하면 더 안전하고 견고한 코드를 작성할 수 있다.
•
읽기 전용 컬렉션 타입
◦
공변성을 가진다.
◦
만약 Rectangle 클래스가 Shape를 상속받는다면 List<Rectangle>은 List<Shape>가 필요한 곳에서 사용할 수 있음을 의미한다.
◦
즉, 컬렉션 타입도 요소 타입과 동일한 서브타입 관계를 가진다.
◦
Map은 Value 타입에서 공변성이 있지만 Key 타입에서는 그렇지 않다.
•
가변 컬렉션 타입
◦
공변성을 가지지 않는다.
◦
만약 MutableList<Rectangle>이 MutableList<Shape>의 서브타입이라면 다른 Shape 상속자를 삽입할 수 있어 Rectangle 타입 인수를 위반하게 된다.
◦
즉, 런타임 오류가 발생할 수 있다.
Collection
fun printAll(strings: Collection<String>) {
for(s in strings) print("$s ")
println()
}
fun main() {
val stringList = listOf("one", "two", "one")
printAll(stringList)
val stringSet = setOf("one", "two", "three")
printAll(stringSet)
}
Kotlin
복사
•
Collection<T>은 컬렉션 계층의 루트이다.
•
이 인터페이스는 읽기 전용 컬렉션의 공통 동작(크기 조회, 항목 포함 여부 확인 등)을 나타낸다.
•
Collection은 요소를 반복하는 작업을 정의하는 Iterable<T> 인터페이스를 상속한다.
•
Collection을 사용하여 다양한 컬렉션 타입에 적용되는 함수를 정의할 수 있다.
•
더 구체적인 경우에는 Collection의 하위 타입인 List 와 Set 을 사용하라.
fun List<String>.getShortWordsTo(shortWords: MutableList<String>, maxLength: Int) {
this.filterTo(shortWords) { it.length <= maxLength }
val articles = setOf("a", "A", "an", "An", "the", "The")
shortWords -= articles
}
fun main() {
val words = "A long time ago in a galaxy far far away".split(" ")
val shortWords = mutableListOf<String>()
words.getShortWordsTo(shortWords, 3)
println(shortWords)
}
Kotlin
복사
•
MutableCollection<T>은 쓰기 작업(add, remove)을 지원하는 Collection이다.
List
val numbers = listOf("one", "two", "three", "four")
println("Number of elements: ${numbers.size}")
println("Third element: ${numbers.get(2)}")
println("Fourth element: ${numbers[3]}")
println("Index of element \"two\": ${numbers.indexOf("two")}")
Kotlin
복사
•
List<T>는 요소를 특정 순서로 저장하고 인덱스를 통해 접근할 수 있게 한다.
•
인덱스는 0부터 시작하며 마지막 인덱스는 (list.size - 1) 이다.
val bob = Person("Bob", 31)
val people = listOf(Person("Adam", 20), bob, bob)
val people2 = listOf(Person("Adam", 20), Person("Bob", 31), bob)
println(people == people2)
bob.age = 32
println(people == people2)
Kotlin
복사
•
List의 요소(null 포함)는 중복될 수 있다.
•
List는 동일한 객체나 하나의 객체를 여러 번 포함할 수 있다.
•
두 리스트는 크기가 같고 같은 위치에 구조적으로 동일한 요소가 있으면 동일한 것으로 간주된다.
val numbers = mutableListOf(1, 2, 3, 4)
numbers.add(5)
numbers.removeAt(1)
numbers[0] = 0
numbers.shuffle()
println(numbers)
Kotlin
복사
•
MutableList<T>는 List 전용 쓰기 작업(특정 위치에 요소 추가, 삭제)을 제공하는 List이다.
List는 Array와 매우 유사하지만 중요한 차이가 있다.
Array의 크기는 초기화 시 정의되며 변경되지 않지만 List는 크기가 고정되지 않으며 변경될 수 있다.
Kotlin에서 MutableList의 기본 구현은 ArrayList이며 크기를 조절할 수 있는 배열이다.
Set
val numbers = setOf(1, 2, 3, 4)
println("Number of elements: ${numbers.size}")
if (numbers.contains(1)) println("1 is in the set")
val numbersBackwards = setOf(4, 3, 2, 1)
println("The sets are equal: ${numbers == numbersBackwards}")
Kotlin
복사
•
Set<T>은 중복되지 않는 요소를 저장하며 요소의 순서는 일반적으로 정의되지 않는다.
•
null 요소 역시 중복되지 않으며 Set에는 null이 한 번만 포함될 수 있다.
val numbers = setOf(1, 2, 3, 4)
val numbersBackwards = setOf(4, 3, 2, 1)
println(numbers.first() == numbersBackwards.first())
println(numbers.first() == numbersBackwards.last())
Kotlin
복사
•
MutableSet은 MutableCollection에서 제공하는 쓰기 작업을 가진 집합이다.
기본 구현체인 LinkedHashSet은 요소 삽입 순서를 유지하므로 first() 나 last() 같은 순서에 의존하는 함수는 예측 가능한 결과를 반환한다.
대안 구현체인 HashSet은 요소의 순서에 대해 아무것도 보장하지 않으며 순서에 의존하는 함수 호출 시 결과가 예측 불가하지만 더 적은 메모리를 사용한다.
Map
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
println("모든 키: ${numbersMap.keys}")
println("모든 값: ${numbersMap.values}")
if ("key2" in numbersMap) println("key2의 값: ${numbersMap["key2"]}")
if (1 in numbersMap.values) println("값 1이 맵에 있습니다.")
if (numbersMap.containsValue(1)) println("값 1이 맵에 있습니다.") // 위와 동일
Kotlin
복사
•
Map<K, V>는 Collection 인터페이스의 상속자가 아니지만 Kotlin 컬렉션 타입 중 하나이다.
•
Map은 Key-Value 쌍(또는 Entry)을 저장하며 Key는 고유해야 하지만 서로 다른 Key가 동일한 Value를 가질 수 있다.
•
Map 인터페이스는 Key로 Value를 접근하거나 Key와 Value를 검색하는 등의 특정 기능을 제공한다.
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)
println("맵은 동일한가요? ${numbersMap == anotherMap}")
Kotlin
복사
•
두 Map이 동일한 쌍을 가지고 있다면 순서에 관계없이 동일한 것으로 간주된다.
val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap.put("three", 3)
numbersMap["one"] = 11
println(numbersMap)
Kotlin
복사
•
MutableMap은 Map에 쓰기 작업이 가능한 변형이다.
•
새로운 Key-Value 쌍을 추가하거나 주어진 Key에 연관된 Value를 업데이트할 수 있다.
기본 구현체인 LinkedHashMap은 요소 삽입 순서를 보존한다.
대안 구현체인 HashMap은 요소의 순서에 대한 보장이 없다.
ArrayDeque
fun main() {
val deque = ArrayDeque(listOf(1, 2, 3))
deque.addFirst(0)
deque.addLast(4)
println(deque) // [0, 1, 2, 3, 4]
println(deque.first()) // 0
println(deque.last()) // 4
deque.removeFirst()
deque.removeLast()
println(deque) // [1, 2, 3]
}
Kotlin
복사
•
ArrayDeque<T>는 양방향 큐의 구현체로 큐의 앞과 뒤에서 요소를 추가하거나 제거할 수 있다.
•
ArrayDeque는 Kotlin에서 스택과 큐 모두의 역할을 할 수 있다.
•
내부적으로는 크기가 자동으로 조정하는 배열을 사용하여 구현된다.