배열 및 컬렉션
배열
배열은 프로덕션에서 잘 사용하지 않는다. 컬렉션을 주로 사용하기 때문이다. Effective Java에서도 배열의 사용보다 리스트의 사용을 권장하고 있다.
그러나 문법은 우선 알아둘 필요가 있다.
코틀린에서는 배열을 어떻게 표현할까?
1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
val array = arrayOf(100, 200)
array.plus(300)
for (i in array.indices) {
println("${i} ${array[i]}")
}
for ((idx, value) in array.withIndex()) {
println("$idx $value")
}
}
- array.plus()
- 값을 쉽게 넣을 수 있다.
- 자바에서 이를 구현하려면 배열을 복사하는 과정이 필요할 것이다.
- array.indices
- 0부터 마지막 index까지의 Range
- array.withIndex()
- 인덱스와 값을 한 번에 가져올 수 있다.
Kotlin Collection
컬렉션을 만들어줄 때 불변인지, 가변인지 설정해야 한다.
- 가변(Mutable) 컬렉션
- 컬렉션에 element를 추가 및 삭제할 수 있다.
- 불변 컬렉션
- 컬렉션에 element를 추가 및 삭제할 수 없다.
자바에서는 Collections.unmodifiableList()와 같다.
따라서 마찬가지로 불변 컬렉션이라 하더라도 Reference Type인 Element의 필드는 바꿀 수 있다는 점을 주의해야 한다.
다만 코틀린에서는 불변/가변을 반드시 지정해주어야 한다는 점이 다른 것이다.
List
기본
1
final List<Integer> numbers = Arrays.toList(100, 200);
1
2
val numbers = listOf(100, 200)
val emptyList = emptyList<Int>()
- listOf() 를 통해 불변 리스트를 만들 수 있다.
- 빈 리스트를 만들기 위해서는 emptyList를 이용하며 타입을 표기해주어야 한다.
단 타입을 추론할 수 있는 경우는 타입을 생략 가능하다. 아래 예시를 보자.
1
2
3
4
val numbers = listOf(100, 200)
printNumbers(emptyList())
private fun printNumbers(numbers: List<Int>) { }
위와 같이 타입을 추론할 수 있는 경우 생략이 가능하다.
값 가져오기
1
2
3
4
5
6
7
8
9
10
11
12
val numbers = listOf(100, 200)
numbers.get(0)
numbers[0]
for (number in numbers) {
println(number)
}
for ((idx, value) in numbers.withIndex()) {
println("$idx, $value")
}
- 자바와 마찬가지로 get() 을 이용해 가져올 수 있다.
- 배열처럼 가져올 수 있다.
- withIndex() 사용이 가능하다.
가변 리스트 생성 방법
1
2
val numbers = mutableListOf(100, 200)
numbers.add(300)
- 자바에 존재하는 리스트 기능들이 다 포함되어 있다.
- 기본 구현체는 ArrayList 이다.
우선 불변 리스트로 만들고 반드시 필요한 경우에 가변 리스트로 바꾸는 방식으로 개발하는 것을 권장한다.
Set
Set은 List와 다르게 순서가 없고 중복된 Element는 존재할 수 없다.
이러한 자료구조적 의미만 제외하면 모든 기능이 List와 유사하다.
1
2
3
4
5
6
7
8
9
10
val numberSet = setOf(100, 200)
val numberMutableSet = mutableSetOf(100, 200)
for (number in numbers) {
println(number)
}
for ((index, number) in numbers.withIndex()) {
println("$index $number")
}
- 기본 구현체는 LinkedHashSet 이다.
Map
우선 자바에서 맵을 다루는 방법을 보자.
1
2
3
4
5
Map<Integer, String> map = new HashMap<>();
map.put(1, "Monday");
map.put(2, "Tuesday");
Map.of(1, "Monday", 2, "Tuesday");
위의 자바 코드를 코틀린으로 바꿔보자.
1
2
3
4
5
6
val oldMap = mutableMapOf<Int, String>()
oldMap.put(1, "Monday")
oldMap[1] = "Monday"
mapOf(1 to "Monday", 2 to "Tuesday") //불변 map
- 타입을 추론할 수 없기 때문에 타입을 표시해주었다.
- 가변 Map이기 때문에 (key, value)를 넣을 수 있다.
- 자바와 같이 put을 쓸 수도 있고 배열처럼 사용할 수도 있다.
- mapOf(key to value)를 사용해 불변 Map을 만들 수 있다.
Map 값 사용
1
2
3
4
5
6
7
8
9
for (key in oldMap.keys) {
println(key)
println(oldMap[key])
}
for ((key, value) in oldMap.entries) {
println(key)
println(value)
}
- keys를 이용해 key값을 이용할 수 있다.
- 마찬가지로 get을 사용해도 되고 배열형식으로 사용할 수도 있다.
- entries
- key와 value를 한 번에 가져올 수 있다.
컬렉션의 null 가능성 / 자바와 함께 사용하기
1
2
3
4
5
6
7
8
List<Int?> :
리스트에 null이 들어갈 수 있지만, 리스트는 절대 null이 아니다.
List<Int>? :
리스트의 요소에는 null이 들어갈 수 없지만 리스트는 null일 수 있다.
List<Int?>? :
리스트에 null이 들어갈 수도 있고, 리스트 자체가 null일 수도 있다.
- 위와 같이 ? 위치에 따라 null 가능성 의미가 달라지므로 차이를 잘 이해하고 주의해서 사용해야 한다.
자바와 함께 사용할 때 주의점
- 자바는 읽기 전용 컬렉션과 변경 가능 컬렉션을 구분하지 않는다.
예를 들어 코틀린에서 불변 리스트를 만들어 사용하고 있다고 가정하자. (listOf)
자바에서 코틀린에서 해당 리스트를 가져오는데 이 때 자바는 불변 리스트인지 구분하지 않는다.
따라서 자바에서 Element를 추가하고 코틀린에서 해당 컬렉션을 다시 가져오면 오동작을 일으킬 수 있다.
- 자바는 nullable 타입과 non-nullable 타입을 구분하지 않는다.
코틀린의 non-nullable 리스트를 자바에서 가져다 쓴다고 가정하자.
그리고 null을 해당 리스트에 추가하고 코틀린이 그 리스트를 다시 가져와 쓰면 오류가 발생할 수 있다.
※ 이러한 경우들을 방지하기 위해서는 코틀린 쪽의 컬렉션이 자바에서 호출될 수 있는 상황이라면 그 부분을 감안하여 다시 컬렉션이 돌아왔을 때의 방어 로직을 짠다거나 코틀린에서 Collections.unmodifiable를 사용하여 변경 자체를 방지하는 방법으로 해결할 수 있다.
- 코틀린에서 자바 컬렉션을 가져다 사용할 때 플랫폼 타입을 신경써야 한다.
1
List<Integer>
위와 같은 컬렉션을 코틀린에서 가져다 쓴다고 가정해보자.
코틀린 입장에서는 아래의 경우들중 어떤 경우인지 판단할 수 없다.
1
2
3
List<Int?>
List<Int>?
List<Int?>?
이런 경우 자바 코드를 보며 맥락을 확인하고, 자바 코드를 가져오는 지점을 wrapping하여 처리해야 한다.
정리
- 배열의 사용법이 약간 다르다.
- 코틀린에서는 컬렉션 생성 시 불변/가변을 반드시 지정해야 한다.
- List, Set, Map에 대한 사용법이 일부 다르다.
- Java에서 Kotlin 코드를 섞어 컬렉션을 사용할 때에는 주의해야 한다.
- Java에서 Kotlin
- 불변 컬렉션의 수정 가능성
- non-nullable 컬렉션에 null값 가능성
- Kotlin에서 Java
- 플랫폼 타입을 주의해야 한다.
- Java에서 Kotlin