Collection

Collection(콜렉션)은 대부분의 프로그래밍 언어에 있는 자료구조입니다. 자바의 List, Map, Set 등을 Collection이라고 합니다. Collection은 Generic으로 구현이 되어 다양한 타입과 함께 사용될 수 있습니다.

코틀린의 Collection은 기본적으로 코트린의 다른 자료구조와 마찬가지로 Mutable(변할 수 없는)과 Immutable(불변의)을 별개로 지원합니다. Mutable로 생성하면 추가, 삭제가 가능하지만, Immutable로 생성하면 수정이 안됩니다.

코틀린의 콜렉션들은 아래 그림과 같은 상속 구조 갖고 있습니다.

(출처: kotlinlang.com)

// Collection 인터페이스
val list: Collection<Int> = listOf(18, 1, 1, 4)  //초기화와 함께 선언
println(list.size) // 4
println(list.isEmpty()) // false (isNotEmpty, none도 있음)
println(18 in list) // true

// MutableCollection 인터페이스
val mutableList: MutableCollection<Int> = mutableListOf(1, 2, 4, 2, 3, 2, 5) // arrayListOf와 동일
mutableList.add(1) 
println(mutableList) // [1, 2, 4, 2, 3, 2, 5, 1]
mutableList.addAll(listOf(3, 2, 4))
println(mutableList) // [1, 2, 4, 2, 3, 2, 5, 1, 3, 2, 4]
mutableList.remove(1)
println(mutableList) // [2, 4, 2, 3, 2, 5, 1, 3, 2, 4]
mutableList.removeAll(listOf(1, 2))
println(mutableList) // [4, 3, 5, 3, 4]
mutableList.retainAll(listOf(3, 5))
println(mutableList) // [3, 5, 3]
mutableList.clear() 
println(mutableList) // []

Collection 반복

val fruits = listOf("apple", "banana", "kiwi")  //리스트 생성
for (item in fruits) {   //리스트 반복하기
  println(item)
}
// 요소의 인덱스를 통해 List에 접근하기
for (index in fruits.indices) {   // 인덱스 지정
  println("fruits[$index] = ${fruits[index]}")   //[]연산자 이용 인덱스 접근
}
// while 루프 이용하기
var index = 0
while (index < fruits.size) {
  println("fruits[$index] = ${fruits[index]}")
  index++
}

 

emptyList() / listOfNotNull() 함수

비어 있는 리스트를 생성하려면 emptyList<>()를 사용할 수 있습니다. 이때는 반드시 형식 매개변수를 지정합니다. listOfNotNull()로 초기화하면 null을 제외한 요소만 반환해 List를 구성할 수 있습니다.

val emptyList: List<String> = emptyList<String>()  // 빈리스트 생성
val nonNullsList: List<Int> = listOfNotNull(2, 45, 2, null, 5, null)  //Null을 제외한 리스트
println(nonNullsList) // [2, 45, 2, 5]

 

List

List는 데이터가 저장하거나 삭제될 때 순서를 지키는 Collection입니다. List는 Mutable(변할 수 없는)과 Immutable(불변의)을 모두 지원합니다.
이 타입의 구현체는 ArrayList, LinkedList 두 가지가 있다 

// List 인터페이스 
val list: List<Double> = listOf(20.18, 1.14, 9.15, 1.14) // 불변형
val list2: List<Double> = mutableListOf(20.18, 1.14, 9.15, 1.14) // 가변형 

val list3: List<Int> = LinkedList<Int>()   //LinkedList와
val list4: List<Int> = ArrayList<Int>()     // ArrayList 2가지가 있다.

println(list[0]) // 20.18
println(list.indexOf(1.14)) // 1
println(list.indexOf(9.31)) // -1
println(list.lastIndexOf(1.14)) // 3
println(list.subList(0, 3)) // [20.18, 1.14, 9.15]

println(list.size) // List 크기
println(list.get(0)) // 해당 인덱스의 요소 가져오기
println(list.contains(2.0)) // 포함 여부 확인하기


List 인터페이스에는 indices()(인덱스의 범위), contains(), get()/elementAt()(해당 인덱스의 요소를 읽기), last(), first(), firstOrNull, lastOrNull, count(), distinct(), joinToString()(요소들의 값을 문자열로 변환하여 결합), single(요소 하나 반환), singleOrNull, binarySearch, find 등이 있다

이외에도 MutableList 인터페이스에는 slice(), containsAll(), asReversed()(모든 요소를 역순으로 저장한 새로운 List를 생성), toList(), toMutableList(), toSet(), toMutableSet(), toHashSet() 등이 있다
LinkedList에서만 사용할 수 있는 함수들은 addFirst(), addLast(), removeFirst(), removeLast() 등이 있다
그 외에도 max(), min() 등의 함수가 존재한다

List : Immutable

listOf<타입>(아이템, )로 Immutable List를 생성 및 초기화를 할 수 있습니다. 코틀린은 아이템의 타입을 추론하기 때문에 타입을 생략해도 됩니다. Immutable이기 때문에 get만 가능합니다. List의 getter는 자바처럼 get(index)도 지원하고 배열처럼 [index]도 지원합니다. 코틀린은 간결하고 직관적인 배열같은 표현 방식을 선호합니다.

val fruits= listOf<String>("apple", "banana", "kiwi", "peach")
// val fruits= listOf("apple", "banana", "kiwi", "peach") -> 타입 생략 가능
println("fruits.size: ${fruits.size}")
println("fruits.get(2): ${fruits.get(2)}")
println("fruits[3]: ${fruits[3]}")
println("fruits.indexOf(\"peach\"): ${fruits.indexOf("peach")}")

위의 코드를 실행한 결과입니다.

fruits.size: 4
fruits.get(2): kiwi
fruits[3]: peach
fruits.indexOf("peach"): 3

 

List : Mutable

수정가능한 List는 mutableListOf로 선언합니다. listOf와 대부분 비슷하지만, 추가 및 삭제가 가능합니다. 자바의 Collection에 익숙하시다면 remove, add, addAll, removeAt 등은 이미 알고 계실 것입니다. 또 slice(), containsAll(), asReversed()(모든 요소를 역순으로 저장한 새로운 List를 생성), toList(), toMutableList(), toSet(), toMutableSet(), toHashSet() 등이 있다
LinkedList에서만 사용할 수 있는 함수들은 addFirst(), addLast(), removeFirst(), removeLast() 등이 있다
그 외에도 max(), min() 등의 함수가 존재한다

val fruits= mutableListOf<String>("apple", "banana", "kiwi", "peach")
fruits.remove("apple")
fruits.add("grape")
println("fruits: $fruits")

fruits.addAll(listOf("melon", "cherry"))
println("fruits: $fruits")
fruits.removeAt(3)
println("fruits: $fruits")

위의 코드를 실행한 결과입니다.

fruits: [banana, kiwi, peach, grape]
fruits: [banana, kiwi, peach, grape, melon, cherry]
fruits: [banana, kiwi, peach, melon, cherry]

그 외에도 replace, replaceAll, contains, forEach 등의 메소드도 지원합니다.

불변형 List를 가변형으로 변환하기

// 불변형 List를 가변형으로 변환하기
val names: List<String> = listOf("one", "two", "three") // 불변형 List

val mutableNames = names.toMutableList()  // 새로운 가변형 List 가 만들어짐
mutableNames.add("four")
println(mutableNames)
// [one, two, three, four]

Set

Set은 동일한 아이템이 없는 Collection입니다. Set의 아이템들의 순서는 특별히 정해져 있지 않습니다. Set은 null 객체를 갖고 있을 수 있습니다. 동일한 객체는 추가될 수 없기 때문에 null도 1개만 갖고 있을 수 있습니다. List와 같이 Set도 Immutable과 Mutable을 별개로 지원합니다.  이 타입의 구현체는 HashSet, LinkedHashSet, TreeSet 세 가지가 있다 

Set : Immutable

setOf<타입>(아이템들)로 객체를 생성할 수 있습니다. 다음과 같이 객체 아이템들을 확인할 수 있습니다.

val numbers = setOf<Int>(33, 22, 11, 1, 22, 3)
println(numbers)
println("numbers.size: ${numbers.size}")
println("numbers.contains(1): ${numbers.contains(1)}")
println("numbers.isEmpty(): ${numbers.isEmpty()}")

실행 결과입니다.

[33, 22, 11, 1, 3]
numbers.size: 5
numbers.contains(1): true
numbers.isEmpty(): false

forEach 또는 Iterator 등으로 모든 객체를 탐색할 수도 있습니다.

Set : Mutable

Mutable은 mutableSetOf<타입>(아이템들) 로 생성할 수 있습니다. Mutable이기 때문에 추가, 삭제가 가능합니다. List와 비슷한 메소드들을 지원합니다.

val numbers = mutableSetOf<Int>(33, 22, 11, 1, 22, 3)
println(numbers)
numbers.add(100)
numbers.remove(33)
println(numbers)
numbers.removeIf({ it < 10 }) // 10 이하의 숫자를 삭제
println(numbers)

실행 결과 입니다.

[33, 22, 11, 1, 3]
[22, 11, 1, 3, 100]
[22, 11, 100]

sortedSetOf() 함수 / linkedSetOf() 함수

sortedSetOf() 함수는 자바의 TreeSet 컬렉션을 정렬된 상태로 반환합니다. 이 함수를 사용하려면 java.util.* 패키지를 임포트해야 합니다. TreeSet는 저장된 데이터의 값에 따라 정렬되는데, 일종의 개선된 이진 탐색 트리(Binary-search Tree)인 레드 블랙 트리(RB tree: Red-Black tree) 알고리즘을 사용해 자료구조를 구성합니다. HashSet보다 성능이 좀 떨어지고 데이터를 추가하거나 삭제하는 데 시간이 걸리지만 검색과 정렬이 뛰어나다는 장점이 있습니다.

linkedSetOf()는 자바의 LinkedHashSet 자료형을 반환하는 헬퍼 함수입니다. 링크드 리스트를 사용해 구현된 해시 테이블에 요소를 저장합니다. HashSet, TreeSet보다 느리지만 데이터를 가리키는 포인터 연결을 통해 메모리 저장 공간을 좀 더 효율적으로 사용할 수 있습니다.

import java.util.*

// 자바의 java.util.TreeSet 선언
val intsSortedSet: TreeSet<Int> = sortedSetOf(4, 1, 7, 2)
intsSortedSet.add(6)
intsSortedSet.remove(1)
println("intsSortedSet = ${intsSortedSet}")
intsSortedSet.clear()  // 모든 요소 삭제
println("intsSortedSet = ${intsSortedSet}")

// Linked List를 이용한 HashSet
val intsLinkedHashSet: java.util.LinkedHashSet<Int> = linkedSetOf(35, 21, 76, 26, 75)
intsLinkedHashSet.add(4)
intsLinkedHashSet.remove(21)

println("intsLinkedHashSet $intsLinkedHashSet")
intsLinkedHashSet.clear()
println(intsLinkedHashSet)

Map

Map은 key와 value를 짝지어 저장하는 Collection입니다. Map의 key는 유일하기 때문에 동일한 이름의 key는 허용되지 않습니다.에 Map 또한 Immutable과 Mutable을 별개로 지원합니다. 이 타입의 구현체는  HashMap,  LinkedHashMap, TreeMap 세 가지가 있다

// Map 인터페이스
val map: Map<String, String> = mapOf("Apple" to "사과", "Banana" to "바나나") // 불변형
println(map.size) // 2
println(map.keys) // [Apple, Banana]
println(map.values) // [사과, 바나나]
println(map.entries) // [Apple=사과, Banana=바나나]
println(map.containsKey("Cocoa")) // false
println(map.containsValue("바나나")) // true
println(map["Apple"]) // 사과
println(map.getOrDefault("Cocoa", "코코아")) // 코코아

// 맵 순회하기
for ((key, value) in map) {
  println("key=$key, value=$value")
}

// MutableMap 인터페이스
val map: MutableMap<String, String> = mutableMapOf() // 가변형 
println(map) // {}
println(map.put("Hi", "안녕1")) // null
println(map) // {Hi=안녕1}
map["Hi"] = "안녕2"
println(map) // {Hi=안녕2}
map.putAll(mapOf("How is it going?" to "잘 지내?", "Bye!" to "잘 가!"))
println(map) // {Hi=안녕2, How is it going?=잘 지내?, Bye!=잘 가!}
println(map.remove("Hi")) // 안녕2
println(map) // {How is it going?=잘 지내?, Bye!=잘 가!}
println(map.remove("Bye!", "잘 가!")) // true
println(map) // {How is it going?=잘 지내?}
map.clear()
println(map) {}

이외에도 getOrPut(k, v), toList(), toMutableMap(), toMap(), toSortedMap(), putAll() 등이 있다

Map의 기타 자료구조

앞에서 Set의 선언된 형태와 비슷하게 Map에서도 자바의 HashMap, SortedMap, LinkedHashMap을 사용할 수 있습니다

import java.util.*

// java.util.HashMap의 사용
val hashMap: HashMap<Int, String> = hashMapOf(1 to "Hello", 2 to "World")
println("hashMap = $hashMap")

// java.util.SortedMap 사용
val sortedMap: SortedMap<Int, String> = sortedMapOf(1 to "Apple", 2 to "Banana")
println("sortedMap = $sortedMap")

// java.util.LinkedHashMap의 사용
val linkedHash: LinkedHashMap<Int, String> = linkedMapOf(1 to "Computer", 2 to "Mouse")
println("linkedHash = $linkedHash")

Map : Immutable

Map은 mapOf<key type, value type>(아이템)로 생성할 수 있습니다. 아이템은 Pair객체로 표현하며, Pair에 key와 value를 넣을 수 있습니다. Pair(A, B)는 A to B로 간단히 표현이 가능합니다. 이런 문법이 가능한 것은 to가 Infix이기 때문입니다.

val numbersMap = mapOf<String, String>(
    "1" to "one", "2" to "two", "3" to "three")
println("numbersMap: $numbersMap")
val numbersMap2 = mapOf(Pair("1", "one"), Pair("2", "two"), Pair("3", "three"))
println("numbersMap2: $numbersMap2")

// 실행해보면 모두 동일한 값을 갖고 있습니다.
// numbersMap: {1=one, 2=two, 3=three}
// numbersMap2: {1=one, 2=two, 3=three}

Map의 데이터를 읽는 것도 다른 Collection과 유사합니다. getter는 get(index)와 [index]를 모두 지원합니다. 코틀린은 배열 방식을 선호합니다. keys와 values는 key와 value만으로 구성된 Set을 리턴해줍니다.

val numbersMap = mapOf<String, String>(
    "1" to "one", "2" to "two", "3" to "three")
println("numbersMap.get(\"1\"): ${numbersMap.get("1")}")
println("numbersMap[\"1\"]: ${numbersMap["1"]}")
println("numbersMap[\"1\"]: ${numbersMap.values}")
println("numbersMap keys:${numbersMap.keys}")
println("numbersMap values:${numbersMap.values}")

for (value in numbersMap.values) {
    println(value)
}

실행 결과입니다.

numbersMap.get("1"): one
numbersMap["1"]: one
numbersMap["1"]: [one, two, three]
numbersMap keys:[1, 2, 3]
numbersMap values:[one, two, three]
one
two
three

Map : Mutable

Mutable은 mutableMapOf<key type, value type>(아이템)로 생성합니다. 객체 추가는 put 메소드이며, Pair를 사용하지 말고 인자로 key와 value를 넣어주면 됩니다. put도 배열 방식을 지원합니다. 그 외에 자바의 Map과 유사합니다.

val numbersMap = mutableMapOf<String, String>(
        "1" to "one", "2" to "two", "3" to "three")
println("numbersMap: $numbersMap")

numbersMap.put("4", "four")
numbersMap["5"] = "five"
println("numbersMap: $numbersMap")

numbersMap.remove("1")
println("numbersMap: $numbersMap")

numbersMap.clear()
println("numbersMap: $numbersMap")

실행 결과 입니다.

numbersMap: {1=one, 2=two, 3=three}
numbersMap: {1=one, 2=two, 3=three, 4=four, 5=five}
numbersMap: {2=two, 3=three, 4=four, 5=five}
numbersMap: {}

Collection 상속

List와 Set은 Collection을 상속합니다. 이 클래스들은 Collection에 대입할 수 있기 때문에 아래와 같이 사용할 수 있습니다.

fun printAll(strings: Collection<String>) {
    for(s in strings) print("$s ")
    println()
}

val stringList = listOf("one", "two", "one")
printAll(stringList)

val stringSet = setOf("one", "two", "three")
printAll(stringSet)

실행 결과 입니다.

one two one
one two three

컬렉션 선택하기

List 인터페이스의 ArrayList와 LinkedList는 기본적으로는 무작위나 순차적인 처리 속도가 더 빠른 ArrayList를 선택하는 것이 좋다. 그러나 List의 중간에서 추가나 삭제를 자주 한다면 LinkedList를 사용하는 것이 좋다. 물론, 정해진 수의 요소들을 처리할 때는 배열을 사용하는 게 좋다
Set 인터페이스에는 LinkedHashSet, HashSet, TreeSet이 있다. 기본적으로는 코클린에서 생성 시 제공되는 LinkedHashSet을 사용하는 것이 좋으며, 무작위 검색의 성능이 매우 중요하다면 HashSet을, 그리고 요소의 일정한 순서 유지와 처리가 중요하다면 TreeSet을 사용하는 것이 좋다
Map 인터페이스는 LinkedHashMap, HashMap, TreeMap이 있다. 코틀린은 기본적으로 LinkedHashMap을 사용한다. HashMap은 무작위 검색 시에만 조금 더 유리하며, 정렬된 상태로 요소를 유지할 필요가 있을 때만 TreeMap을 사용하는 게 좋다. 그리고 Map은 크기가 성능을 좌우하므로 할당된 크기에 너무 많은 요소가 저장되지 않도록 하는 것이 중요하다

 정렬

정렬에는 sorted(), sortedBy(), sortedDescending(), sortedDescendingBy()가 있다

정리

코틀린의 Collection인 Map, Set, List에 대해서 알아보았습니다. 자바와 다른 것은 Mutable과 Immutable을 구분하여 지원한다는 점입니다. 그 외의 지원하는 메소드는 자바와 거의 유사합니다.

 

'Kotlin > Collection' 카테고리의 다른 글

Collection Sort  (0) 2022.09.12

+ Recent posts