[참고 도서]
- 다재다능 코틀린 프로그래밍
콜렉션 특징
- 자바 List, Set, Map을 코틀린에서도 사용한다
- 코틀린에서는 자바의 뮤터블 콜렉션 인터페이스를 이뷰터블 읽기 전용, 뮤터블 읽기/쓰기 인터페이스으로 나뉜다
- JDK 제공 함수 이외의 코틀린 콜렉션에 메소드가 추가되었다
kotlin.collections
패키지에 자바 콜렉션에 유용하게 사용할 수 있는 함수가 추가되었다
- 콜렉션의 요소들을 반복문으로 사용할 때 자바보다 더 유연하고 직관적으로 사용할 수 있다
- Pair - 값이 두 개인 튜플
- Triple - 값이 세 개인 튜플
- Array - 객체나 프리미티브 타입으로 순번이 있고 크기가 고정된 콜렉션
- List - 객체들이 정렬된 콜렉션
- Set - 객체들이 정렬되지 않은 콜렉션
- Map - 키와 값
코틀린 콜렉션에 추가된 메소드
var list = listOf(1, 2, 3)
print(list.javaClass) // class java.util.Arrays$ArrayList
for ((index, value) in list.withIndex()) { print("index: $index, value: $value") } // 012
// withIndex
public fun <T> Iterable<T>.withIndex(): Iterable<IndexedValue<T>> {
return IndexingIterable { iterator() }
}
- listOf()로
JDK의 ArrayList
객체를 가져온다 withIndex()
로 리스트의 인덱스와 값을 가져온다IndexedValue
라는 특별한 interator를 반환한다
- 코틀린 컬렉션의 편의를 위해 JDK 클래스에 추가해둔 메소드를 사용한다
뷰
- 이뮤터블 콜렉션은 동시성을 사용하는 함수형 프로그래밍이나 비동기 처리에 안정적이다
- 대부분 자바의 콜렉션은 대부분 뮤터블이였으며 최근에 이뮤터블 콜렉션이 추가되었다
- 그러나 이뮤터블, 뮤터블 버전이
모두 같은 인터페이스를 구현하고 있기에 문제가 발생한다
- 이뮤터블 콜렉션을 변경하려는 경우
런타임
에UnsupportedOperationException
이 발생한다
- 그러나 이뮤터블, 뮤터블 버전이
- 코틀린은 이뮤터블 콜렉션을 변경하려는 것처럼
연산이 불가능한 것을 런타임이 되기 전에 알려주고자
뷰
가 존재한다 - List, Set, Map 각각 뷰를 두 가지씩 갖고 있다
- 읽기 전용 뷰인 이뮤터블 뷰
- 쓰기를 시도하면 컴파일 단계에서 실패한다
- 읽기쓰기 뷰인 뮤터블 뷰
- 이뮤터블, 뮤터블 뷰 모두 자바 기본 콜렉션에 매핑되지만 뷰를 사용하여
런타임 오버헤드가 없고 컴파일 시간이나 실행 시간에 변환이 발생하지 않는다
- 읽기전용의 참조도 뮤터블 콜렉션이다
- 콜렉션 자체는 변경할 수는 없지만 다른 스레드에서 참조하고 있는 콜렉션을 변경 시도할 수 있다
- 여러 개의 뷰에서 같은 인스턴스를 참조하고 있고 어떤 스레드에서는 해당 인스턴스를 읽기쓰기용으로 사용하고 다른 스레드는 읽기전용으로만 사용하고 있다면 동일한 콜렉션을 변경하지 않도록 해야한다
- 따라서 스레드 안정성을 제공해주지 않는다
- 읽기 전용 뷰인 이뮤터블 뷰
Pair와 Triple
튜플
- 작고 셀 수 있는 크기의 객체의 배열
- 코틀린에서는 2, 3개 사이즈의 튜플만을 허용한다
- Pair - 2개 사이즈의 튜플
- Triple - 3개 사이즈의 튜플
Pair
- Pair에 담긴 값은 pair.first, pair.second로 가져올 수 있다
val pair = Pair<Int, String>(1, "튜플") // 각각의 타입을 다르게 넣을수도 있다
>>> (1, 튜플)
// pair.first == 1
// pair.second == 튜플
val mapPair = mapOf("Tom" to "Cat", "Jerry" to "Mouse")
>>> {Tom=Cat, Jerry=Mouse}
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
는 키-값 형태를 반환하는데 인자로to()
메소드로 만든Pair 인스턴스
를 받는다to() 메소드는 Pair의 인스턴스를 만든다
- 코틀린의 모든 객체에서 사용할 수 있는 확장함수
- mapOf는 Pair를 인자로 받는다
"Tom" to "Cat"
,"Jerry" to "Mouse"
가 각각 2개 사이즈 튜플인 Pair이다- Tom과 Cat으로 구성된 Pair + Jerry와 Mouse로 구성된 Pair
- 키는 Tom, Jerry이고 값은 Cat, Mouse
Triple
-
3개 사이즈를 갖는 튜플로 first, second, third로 각각 값을 가져온다
var triple = Triple<String, Int, Boolean>("트리플", 1, true) println(triple) println("${triple.first}, ${triple.second}, ${triple.third}") >>> (트리플, 1, true) >>> 트리플, 1, true
-
Object 형태의 각각 클래스를 만들어주는 작업이 필요하지 않고 간결하게 타입 안정성까지 제공하는Pair로 코드를 작성할 수 있다
val airportCodes = listOf("A", "B", "C") var temperatures = airportCodes.map { code -> code to 1 } for (temp in temperatures) { println("temp: $temp") println("Airport: ${temp.first}, Temperature: ${temp.second}") } >>> temp: (A, 1) Airport: A, Temperature: 1 temp: (B, 1) Airport: B, Temperature: 1 temp: (C, 1) Airport: C, Temperature: 1
- listOf로 만든 리스트를
- map으로 돌면서
- to를 이용해 Pair로 만들었다
⇒ map으로 기존 airportCodes로 temperatures 생성하였고 List<Pair<String, Int>> 형태가 되었다
-
객체 쌍이 필요하거나 튜플이 필요한 곳에 Pair, Triple이 유용하다
- 간결하고 컴파일 시간에 타입 안정성을 제공한다
-
Pair, Triple 모두
immutable
이다
객체 배열과 프리미티브 배열
-
Array
클래스로 낮은 수준의 최적화에서만 사용 - 뮤터블
- []의 인덱스 연산자 접근
- 그 외에는 List 사용
-
arrayOf
라는 최상위 함수를 사용해 배열을 만든다val array = arrayOf(1, 2, 3) println(array::class) // class kotlin.Array println(array.javaClass) // class [Ljava.lang.Integer; println(array[0]) // 1
- arrayOf로 생성한 배열은 코틀린에서는 Array
를 갖지만 JVM에서 보면 Integer 배열이다 - Integer 클래스로 생성되었고
프리미티브 타입인 int를 사용한 경우보다 오버헤드가 크게 걸린다
- 따라서
intArrayOf(1, 2, 3)
처럼 사용하여kotlin.Array
대신에kotlin.IntArray
타입 특화 배열 클래스를 사용할 수 있도록 한다 kotlin.Array
내장된 메소드들(size, sum, average...)을 사용해 배열 데이터 계산을 할 수 있다
- Integer 클래스로 생성되었고
- arrayOf로 생성한 배열은 코틀린에서는 Array
List
- 뮤터블, 이뮤터블을 선택하여 사용한다
- 정렬 + 길이 가변
- []의 인덱스 연산자로 접근하거나 get() 메소드를 사용한다
- 인덱스 연산자는 내부적으로 get() 메소드를 사용한다
- 결국 노이즈를 줄이기 위해서 인덱스 연산자를 사용하기를 권장한다
- listOf() - 이뮤터블 리스트
- kotlin.collections.List
- kotlin.collections.List
- mutableListOf() - 뮤터블 리스트
B.contains(A)
나A in B
로 A라는 값이 리스트 B에 존재하는지 확인한다in
을 사용하는게 더 표현력이 높고 직관적이다...
listOf
-
리턴하는 참조를 변경할 수 없다
- 읽기 전용 참조를 리턴한다
val fruits = listOf("사과") fruits.add("배") // Kotlin: Unresolved reference: add
-
List
는 자바의 Arrays.asList()로 만든 JDK 객체의 뷰로 동작한다 - 해당 인터페이스에는 mutation 권한을 가진 메소드가 없기 때문에
add()
메소드가 컴파일 단계에서 실패한다 - 이런 뷰를 이용해 코틀린의 코드를 안전하게 만들고 실행 시간에 오버헤드나 변경이 없게 만든다
- 그러나
+, -
연산자로 리스트를 변경할 수 있다.................
val fruits = listOf("사과") val fruits2 = fruits + "배" val fruits3 = fruits2 - "배" println(fruits) // [사과] println(fruits2) // [사과, 배] println(fruits3) // [사과] println(fruits::class) // class java.util.ArrayList println(fruits.javaClass) // class java.util.ArrayList
+, -
연산자로 변경하는 경우 기존 리스트를 복사해서 새로운 리스트를 반환하기에 기존의 리스트는 유지된다class java.util.Arrays$ArrayList
가 나온다고 하는데 나는 이게 아닌데...?
- 해당 인터페이스에는 mutation 권한을 가진 메소드가 없기 때문에
mutableListOf
-
MutableList
-
List
에서 사용하던 것과 동일하게 사용 가능하다 val fruits = mutableListOf("사과") fruits.add("배") println(fruits) // [사과, 배] println(fruits::class) // class java.util.ArrayList println(fruits.javaClass) // class java.util.ArrayList
- 읽기 쓰기 모두 가능한 인터페이스이지만 listOf()로 이뮤터블한 리스트를 사용하는 것이 좋다
Set
- Set
- 정렬되지 않은 요소의 모음
-
List
처럼 immutable, mutable 둘 다 존재하며 메소드들을 지원한다 -
setOf(), mutableSetOf(), hashSetOf(), linkedSetOf(), sortedSetOf()
-
중복 요소를 허용하지 않는다
val fruits = setOf("사과", "배", "배") println(fruits) // [사과, 배] println(fruits::class) // class java.util.LinkedHashSet println(fruits.javaClass) // class java.util.LinkedHashSet
-
Map
-
Map<K, V>
-
키-값 Pair를 보관하는 콜렉션
- immutable, mutable 모두 존재한다
- mapOf(), mutableMapOf(), hashMapOf(), linkedMapOf(), sortedMapOf()
val sites = mapOf("key" to "value", "키" to "값") println(sites) // {key=value, 키=값} println(sites::class) // class java.util.LinkedHashMap println(sites.javaClass) // class java.util.LinkedHashMap
-
to()
확장함수를 이용해 키-값 Pair를 만든다 -
메소드를 이용해 키 또는 값이 존재하는지 확인할 수 있다
sites.containsKey("키")
sites.containsValue("값")
sites.contains("키")
"키" in sites
-
get()
메소드나 [] 인덱스 연산자로 해당 키에 대한 값을 가져온다val value: String? = sites.get("키가없다") >>> null val value: String? = sites["키가없다"] >>> null val value: String = sites.getOrDefault("키가없다", "기본값 반환") >>> 기본값 반환
- 만약 해당 키에 대한 값이 없다면 오류가 발생한다
- nullable 타입 사용하거나 디폴트 값을 반환하는 메서드를 사용
- 만약 해당 키에 대한 값이 없다면 오류가 발생한다
-
mapOf()
은 읽기 전용 참조로 변경이 불가능 하지만+, -
연산자로 새로운 맵을 만들 수 있다val sites2 = sites + ("새로운키" to "값")
-
key, value
속성값으로 맵의 키, 값을 가져온다for (site in sites) { println("${site.key} -> ${site.value}") } // 구조분해 for ((key, value) in sites) { println("${site.key} -> ${site.value}") }
결론
- 코틀린에서는 자바 콜렉션 확장과 읽기전용 뷰를 통해 컴파일 시간동안 안정성을 높인다
- 함수형, 동시성, 비동기 코드를 작성 시 읽기전용 뷰를 사용해야한다
- Pair와 Triple로 크기가 작은 콜렉션을 만든다
- 크기가 크고 고정된 콜렉션은 Array 클래스를 사용하며 크기가 변경되는 경우에는 List나 Set을 사용한다
- 콜렉션을 사용할때 해당 콜렉션이 mutable인지 immutable인지 선택해서 생성한다
- 읽기전용 뷰, 읽기쓰기 뷰 차이
🚨?!?!?
- 런타임/실행 시간
- 내용 중 두 단어가 혼용되어있는데 같은 것이 아닌건가...
- 단어를 통일하는게 좋겠다
- get() 보다 [] 인덱스 연산자
- 인덱스 연산자 내부적으로 get()을 사용하는데 바로 사용하는 것보다 인덱스로 접근하는게 왜 더 안정적인 것일까?
🙈 더 알아보기
- mutable vs immutable
- 뮤터블 콜렉션 인터페이스 - 간단한 싱글 스레드
- mutableListOf, mutableSetOf...
- 이뮤터블 콜렉션 인터페이스 - 함수형 비동기
- listOf, setOf...
- 뮤터블 콜렉션 인터페이스 - 간단한 싱글 스레드
- 최상위 함수
- 클래스나 다른 함수 안에 있는 것이 아닌 가장 바깥에 존재하는 함수
- static
- 같은 Package라면 import하지 않아도 바로 접근이 가능
- 다른 Package라면
import package
필요
- 다른 Package라면
- 클래스나 다른 함수 안에 있는 것이 아닌 가장 바깥에 존재하는 함수
- 코드 노이즈