콜렉션 사용하기

·2021년 12월 7일
0
post-thumbnail

Java에서 사용 가능한 콜렉션은 코틀린에서 바로 사용 가능하며, 추가로 콜렉션에 대한 몇 가지 뷰 인터페이스를 제공한다.

예를 들어서, 코를린의 페어(Pair)와 트리플(Triple)은 각각 2개, 3개의 값들을 위한 콜렉션이다. 더 많은 값을 갖는 고정된 크기의 콜렉션으로 배열을 사용할 수 있다. 하지만 유연성을 위해서 리스트를 사용하는 경우가 더 많을 것이다.
코틀린은 셋과 맵을 포함해 위의 모든 것을 제공해준다.

코틀린은 Java 콜렉션으로 제공하는 뮤터블 콜렉션 인터페이스 뷰와 이뮤터블 콜렉션 인터페이스 뷰 모두를 제공한다.

프로그램이 간단한 싱글 스레드라면 뮤터블 콜렉션 인터페이스를 사용하고, 더 복잡한 함수형 비동기 프로그램이라면 이뮤터블 콜렉션 인터페이스를 사용하는게 더 안전하다.

📌 콜렉션의 특징


Java에서 우리는 리스트(List), 셋(Set), 맵(Map) 등등 여러 종류의 콜렉션을 모두 사용할 수 있다.
Java의 뮤터블 콜렉션 인터페이스는 코틀린에서 이뮤터블 읽기전용 인터페이스와 뮤터블 읽기-쓰기 안터페이스로 나뉘어 진다.

콜렉션의 요소들은 반복문으로 사용할 때 Java보가 더 쉽고 유연하고 직관적으로 반복문을 사용할 수 있다.

pair - 값이 두 개인 튜플
Triple - 값이 세 개인 튜플
Array - 객체나 프리미티브(기본형) 타입으로 구성되어 순번이 있고, 크기가 고정된 콜렉션
List - 객체들이 정렬된 콜렉션
Set - 객체들이 정렬되지 않은 콜렉션
Map - 연관 사전 혹은 키와 값의 맵

코틀린이 제공하는 편리한 메소드들

코틀린은 withIndex() 메소드를 제공해서 인덱스와 값 모두를 편하게 얻게 해준다.

입력

val names= listOf("Tom","Jerry")
println(names.javaClass)
for((index,value) in names.withIndex()){
    println("$index $value")
}

💻 출력

class java.util.Arrays$ArrayList
0 Tom
1 Jerry

코틀린의 listOf() 메소드를 이용해 JDK에서 가져온 ArrayList객체를 가지고 오고, 그 객체에 있는 withIndex()메소드를 호출했다.

withIndex()는 코틀린이 JDK클레스에 편의를 추가해놓은 수많은 메소드 중 하나일 뿐이다.

kotlin.collections 패키지를 보면 여러 메소드들을 확일할 수 있다.

Java의 대부분 콜렉션은 뮤터블이다. 최근 몇년간 이뮤터블 콜렉션을 선보였지만 뮤터블, 이뮤터블 모두 같은 인터페이스를 구현하고 있다는 문제를 가지고있다.
그래서 이뮤터블 콜렉션을 변경하려는 모든 시도를 하면 실행 시간에 UnsupportedOperationException이 나온다.

이와 달리 코틀린은 연산이 불가능 하다는 것을 실행 시간이 되어서야 알리지 않는다. 그게 코틀린에 뷰가 있는 이유이다.

리스트,셋,맵은 각기 뷰를 두 가지씩 가지고 있다.

✔ 읽기전용 뷰인 이뮤터블 뷰
✔ 읽기-쓰기 전용 뷰로 불리는 뮤터블 뷰다.

이 뷰들을 사용하면 런타임시 오버헤드가 없고, 컴파일 시간이나 실행 시간에 변환이 발생하지 않는다.

읽기전용 뷰에는 읽기 연산자만 사용 가능하며 쓰기를 시도할 경우 컴파일 단계에서 실패한다.

읽기전용 참조는 뮤터블 콜렉션이다.   다른 스레드가 참조하고 있는 콜렉션을 변경하지 않는다는 걸 보장해주지 않는다.

📌 페어와 트리플 사용하기


튜플은 작고 셀 수 있는 크기의 객체의 배열이다. 코틀린은 두 개의 사이즈의 튜플만을 허용한다.
튜플은 빠르게 2개나 3개의 객체를 콜렉션으로 만들고 싶을 때 사용한다.

println(Pair("Tom","Jerry"))//(Tom, Jerry)
println(mapOf("Tom" to "Cat","Jerry" to "Mouse"))//{Tom=Cat, Jerry=Mouse}

mapOf는 Pair의 생성자를 이용해서 인스턴스를 만든 후 to() 확장함수를 이용해서 Map의 엔트리가 될 페어를 만들었다.
to() 메소드는 Pair의 인스턴스를 만든다. 앞에 나온 값이 Pair의 첫번째 값이고, 뒤에 나온 것이 두번째 값이 된다.

입력

val airportCodes = listOf("LAX", "SFO", "PDX", "SEA")
val temperatures = airportCodes.map { code -> code to getTemperatureAtAirport(code) }
for (temp in temperatures) {
    println("Airport: ${temp.first}:Temperature: ${temp.second}")
}

fun getTemperatureAtAirport(code: String): String = "${Math.round(Math.random() * 30) + code.count()} C"

💻 출력

Airport: LAX:Temperature: 21 C
Airport: SFO:Temperature: 22 C
Airport: PDX:Temperature: 27 C
Airport: SEA:Temperature: 4 C

airportCodes 콜렉션을 map() 반복자를 이용해서 반복했다.
airportCodes가 가지고 있던 공항 코드는 (코드,온도) 형태인 Pair로 생성된다
List<Pair<String,Stirng>> 형태

Pair는 간결한 코드를 만들 수 있을 뿐만 아니라 컴파일시간에서 타입 안전성 또한 제공한다.

3개의 객체가 필요하다면 Triple을 사용하면 된다.

페어와 트리플 모두 이뮤터블이다. 두 클래스는 값을 두 개씩 또는 세 개씩 반복적으로 그룹핑할 때 유용하다.

📌 객체 배열과 프리미티브 배열


Array<T> 클래스는 코틀린의 배열을 상징한다. 배열은 낮은 수준의 최적화가 필요할 때만 사용하고, 그 외에는 List와 같은 다른 자료구조를 사용하는것이 좋다.

String 배열을 만들기 위해서는 arrayOf() 에 원하는 값들을 넘겨주면 된다.

val friends = arrayOf("Tintin", "Snowy", "Haddock", "Calculus")
println(friends::class)//class kotlin.Array
println(friends.javaClass)//class [Ljava.lang.String;
println("${friends[0]} amd ${friends[2]}")//Tintin amd Haddock

정수 배열도 마찬가지로 arrayOf()를 이용해 만들 수 있다.

val numbers = arrayOf(1,2,3)
println(numbers::class)//class kotlin.Array
println(numbers.javaClass)//class [Ljava.lang.Integer;

위의 코드는 동작하지만 바람직하지 않다.

arrayOf()에 숫자가 전달되면 Array<T>가 만들어지긴 하지만 내부적으로 봤을 때 Integer 클래스의 배열이 만들어진다. Integer 클래스로 작업하면 프리미티브 타입 int를 사용할 때에 비해서 오버헤드가 크게 걸린다.

val numbers = intArrayOf(1,2,3)
println(numbers::class)//class kotlin.IntArray
println(numbers.javaClass)//class [I

Array<T>에 사용된 연산은 IntArray 같이 타입 특화 배열 클래스에서도 동일하게 사용 가능하다.

입력

println(numbers.size)
println(numbers.average())

💻 출력

3
2.0

배열을 만들 때 하드코딩으로 값을 적는 대신에 값을 계산해서 넣은 수도 있다.

입력

println(Array(5){i-> (i+1)*(i+1)}.sum())

💻 출력

55

Array의 생성자는 파라미터로 배열의 사이즈와 0부터 시작하는 인덱스를 받아 해당위치에 있는 값을 리턴해주는 함수를 받는다.

📌 리스트 사용하기


리스트를 만드는 첫 단계에서 코트린은 개발자에게 이뮤터블 또는 뮤터블인지를 선택하도록 한다.
이뮤터블 리스트는 listOf() 메소드, 뮤터블 리스트는 mutableListOf()를 만들면 된다.
둘 중 하나를 선택하는 경우라면 이뮤터블을 사용하는것이 좋다.

listOf()

listOf() 함수는 kotlin.collections.List<T> 인터페이스 참조를 리턴한다.

val fruits: List<String> = listOf("Apple", "Banana", "Grape")
println(fruits)//[Apple, Banana, Grape]

리스트 접근

리스트의 요소들을 접근하기 위해서 get()메소드 또는 인덱스 연산자[ ]를 사용한다.

println("first's ${fruits[0]}, that's ${fruits.get(0)}")//first's Apple, that's Apple

💡인덱스 연산자를 사용하는 편이 get() 보다 노이즈가 적고 편리하다.

리스트 값 여부 확인

콜렉션에 값이 있는지 없는지 확인하기 위해서 contains()메소드 또는 in 연산자를 사용할 수 있다.

println(fruits.contains("Apple"))//true
println("Apple" in fruits)//true

💡in을 사용하는 게 더 표현력이 좋고, 직관성이 있다.

리스트 값 변경

fruits.add("Orange") //ERROR Unresolved reference: add

kotlin.collections.List<T>의 인터페이스는 Arrays.asList()로 만든 JDK 객체의 뷰로 동작한다. 이 인터페이스는 변화를 허용하거나 리스트를 바꿀 수 있는 권한을 가진 메소드가 없다.
그래서 add()메소드를 호출하면 실패하게 된다.

이런 보호조치는 바람직하다. 이런 뷰를 제공해주기 때문에 코틀린은 코드를 더 안전하게 만들고 실행시간에 오버헤드나 변경이 없게 만든다.

하지만 이런 보호조치로 모든 경우를 다 막을 수 없다.

val fruits2 = fruits + "Orange"
println(fruits)//[Apple, Banana, Grape]
println(fruits2)//[Apple, Banana, Grape, Orange]

이 연산은 fruits를 변경시키진 않지만 기존 리스트를 카피하면서 새로운 리스트를 만들고 새로운 요소를 추가한다.

➕ 연산자가 요소를 추가한다면 ➖ 연산자는 기존 리스트에서 특정요소를 제외해 준다.

val fruits3 = fruits - "Banana"
println(fruits)//[Apple, Banana, Grape]
println(fruits3)//[Apple, Grape]

mutableListOf()

List<T>에서 사용하던 모든 기능들이 mutableListOf()에서 사용 가능하다.
mutableListOf() 메소드를 이용해서 생성된 인스턴스는 java.util.Arrays$ArrayList가 아니고 java.util.ArrayList이다

val fruitsMutable: MutableList<String> = mutableListOf("Apple", "Banana", "Grape")
println(fruitsMutable::class)//class java.util.ArrayList

위의 코드의 fruitsMutable는 일기전용이 아닌 읽기-쓰기 모두가 가능한 인터페이스를 얻을 수 있다.

fruitsMutable.add("Orange")

리스트에 요소를 추가하는 것도 가능하다

📌 셋 사용하기


셋은 정렬되지 않은 요소의 모음이다.
List<T>를 만드는 메소드처럼 이뮤터블/읽기전용 버전과 뮤터블/읽기-쓰기 버전 모두가 있다.

Set<T>의 인스턴스를 만들기 위해서는 setof()를 사용하고 MutableSet<T>을 만들기 위해서는 mutableSetOf()를 이용한다.

입력

val fruits= setOf("Apple","Banana","Apple")
println(fruits)

💻 출력

[Apple, Banana]

셋은 중복을 허용하지 않기 때문에 셋이 만들어질 때 중복값이 누락된다.

println(fruits::class)//class java.util.LinkedHashSet
println(fruits.javaClass)//class java.util.LinkedHashSet

Set<T> 와 MutableSet<T> 도 List<T>처럼 +, -, contains, in 등 많은 함수들을 포함하고 있다.

📌 맵 사용하기


맵은 키-값 페어를 보관하는 콜렉션이다.
코틀린은 JDK의 맵을 위한 두 가지 인터페이스를 제공한다. JDK의 맵에서 사용 가능한 모든 메소드는 뮤터블 인터페이스에서 사용 가능하다. 이뮤터블 인터페이스에서는 읽기전용 메소드들만 사용 가능하다.

mapOf()를 사용해서 Map<K,V>의 읽기전용 맵을 만들 수 있다.
mutableMapOf()를 사용하면 MutableMap<K,V>의 맵을 만들 수 있다.

mapOf

val sites = mapOf(
    "google" to "https://www.google.co.kr",
    "youtube" to "https://www.youtube.com"
)

println(sites.size) //2

키-값 페어가 to() 확장함수를 통해 만들어진다.
to() 확장함수는 코틀린 모든 객체에서 사용 가능하고, MapOf()는 Pair<K, V>를 인자로 취급한다.

containsKey(), containsValue() 메소드를 이용해 맵안에 키 또는 값이 존재하는지 체크해볼 수 있다.

println(sites.containsKey("google"))//true
println(sites.containsValue("ttps://www.naver.com"))//false
println(sites.contains("youtube"))//true
println("google" in sites)//true

get()

키에 해당하는 값에 접근하기 위해 get()메소드를 사용할 수 있다.

//ERROR : Type mismatch: inferred type is String? but String was expected
val googleSite : String=sites.get("google") 

위의 코드가 작동하지 않는 이유는 get() 메소드는 키가 맵에 없을 경우 nullable 타입을 리턴하기 때문에 nullable 참조 타입을 써야한다.

val googleSite: String? = sites.get("google")//true
val googleSite2: String? = sites["google"]//true

아니면 getOrDefault()을 이용해 디폴트 값을 적어준다

val googleSite3 = sites.getOrDefault("google", "https://www.google.co.kr")

맵에 "google"라는 키가 없다면 두 번째 인자를 리턴한다.

맵 변경하기

mapOf() 함수는 읽기전용 참조만 전달하기 때문에 맵을 변경 할 수 없다.
하지만 키-값 Pair를 추가해서 새로운 맵을 만들 수 있다.

리스트와 마찬가지로 ➕ 연산자와 ➖ 연산자를 이용한다.

val siteWithExample = sites + ("example" to "https://www.example.com")
val withoutGoole = sites - "google"

for 루프

for 루프를 사용해 맵의 요소들을 가져올 수 있다.

for (entry in sites) {
    println("${entry.key}---${entry.value}")
}

변수 entry는 맵의 요소들을 참조해 키와 값을 가져온다.
구조분해를 이용하면 더 간단하게 가져올 수 있다.

for ((key, value) in sites) {
    println("$key---$value")
}

반복이 진행되면서 코틀린은 자동으로 key와 value를 각각의 요소에서 추출하여 이뮤터블 변수인 key와 value에 할당한다.

맵 인터페이스는 getValue()와 setValue()라는 특별한 메소드를 가지고 있는데 두 메소드는 맵을 대리자로 사용 가능하게 해주는 메소드이다.
이에 관한 내용은 ch9:델리게이션을 통한 확장에서 다룰 예정이다.



🔑 정리


코틀린은 Java의 콜렉션을 확장하는 동시에 읽기전용 뷰를 통해서 컴파일 시간의 안전성을 향상시켰다. 함수형 코드를 쓰거나, 동시성 코드를 작성하거나, 비동기 프로그램을 만들 떄는 읽기전용 뷰를 사용해야 한다.

페어와 트리플은 한정된 작은 크기의 콜렉션을 만들기에 유용하고 크기가 크고 고정된 크기의 콜렉션을 만들 때는 Array 클래스를 사용하는것이 좋다.
크기가 변경되는 콜렉션이라면 리스트와 셋 중에서 골라 사용하자

콜렉션을 사용할 때는 콜렉셩 관련 메소드를 선택해야 한다.(listOf() / mutableListOf() . . .)



출처 : 다재다능 코틀린 프로그래밍

profile
개발하고싶은사람

0개의 댓글