컬렉션은 단어 목록이나 직원 기록 모음과 같은 관련 항목 그룹이다. 컬렉션의 항목은 순서가 지정되거나 지정되지 않을 수 있으며 고유하거나 고유하지 않을 수 있다. 목록 역시 컬렉션의 한 유형이다.
Kotlin의 또 다른 컬렉션 유형은 집합이다. 목록과 달리 중복된 원소를 가질 수 없다.
val numbers = listOf(0, 3, 8, 4, 0, 5, 5, 8, 9, 2)
println("list: ${numbers}")
println("sorted: ${numbers.sorted()}")
val setOfNumbers = numbers.toSet()
println("set: ${setOfNumbers}")
실행결과
list: [0, 3, 8, 4, 0, 5, 5, 8, 9, 2]
sorted: [0, 0, 2, 3, 4, 5, 5, 8, 8, 9]
set: [0, 3, 8, 4, 5, 9, 2]
목록을 toSet()
을 이용하여 집합으로 바꾸자 중복된 원소가 저절로 사라졌다.
또한 집합은 순서가 없다.
val set1 = setOf(1,2,3)
val set2 = mutableSetOf(3,2,1)
println("$set1 == $set2: ${set1 == set2}")
실행 결과
[1, 2, 3] == [3, 2, 1]: true
집합에서 할 수 있는 기본 작업 중 하나는 contains()
함수를 사용하여 특정 항목이 집합에 속하는지 여부를 확인하는 것이다.
println("contains 7: ${setOfNumbers.contains(7)}")
이외에도 교집합(∩) 또는 합집합(∪)과 같은 연산을 intersect()
또는 union()
을 사용하여 실행할 수 있다.
맵 또는 사전 역시 컬렉션 유형의 하나이다. 맵은 특정 키가 부여된 값을 쉽게 찾을 수 있도록 설계된 키-값 쌍의 집합이다.
fun main() {
val peopleAges = mutableMapOf<String, Int>(
"Fred" to 30,
"Ann" to 23
)
println(peopleAges)
//항목을 더 추가하려는 경우
peopleAges.put("Barbara", 42)
//혹은
peopleAges["Joe"] = 51
}
실행결과
{Fred=30, Ann=23, Barbara=42, Joe=51}
이미 있는 키 쌍에 대한 값을 업데이트할 수도 있다.
peopleAges["Fred"] = 31 //이제 프레드의 나이는 31살
컬렉션의 모든 객체를 열거하는 것은 일반적인 작업이므로, Kotlin에서는 자동으로 모든 항목을 탐색한 후 항목별로 작업을 실행하는 forEach()
를 제공한다.
peopleAges.forEach { print("${it.key} is ${it.value}, ") }
실행결과
Fred is 31, Ann is 23, Barbara is 42, Joe is 51,
forEach
는 현재 항목의 변수를 지정하는 대신 특수 식별자 it
을 사용한다.
위에서 나온 Map과는 다름!
map
함수는 컬렉션의 각 항목에 변환을 적용한다.
위의 forEach
실행결과에서 마지막에 붙은 쉼표를 없애기 위해..
println(peopleAges.map { "${it.key} is ${it.value}" }.joinToString(", ") )
실행결과
Fred is 31, Ann is 23, Barbara is 42, Joe is 51
peopleAges.map
은 peopleAges
의 각 항목에 변환을 적용하고 변환된 항목으로 이루어진 새 컬렉션을 만든다.
중괄호 {} 안에 있는 부분은 각 항목에 적용할 변환을 정의합니다. 키-값 쌍을 가져와서 문자열로 변환한다. 예를 들어 <Fred, 31>을 Fred is 31로 변환된다.
joinToString(", ")
은 변환된 컬렉션의 각 항목을 문자열에 추가하고 ,로 구분하며 마지막 항목에는 기호를 추가하지 않는다.
이 모든 과정은 점 연산자(.
)로 결합된다.
컬렉션의 또 다른 작업은 특정 조건과 일치하는 항목을 찾는 것이다. filter()
함수는 컬렉션에서 표현식을 기반으로 일치하는 항목을 반환한다.
filter
호출에는 괄호가 필요 없으며, it
은 목록의 현재 항목을 나타냅니다.
val filteredNames = peopleAges.filter { it.key.length < 4 }
println(filteredNames)
실행결과
{Ann=23, Joe=51}
앞에서 배운 코드
peopleAges.forEach { print("${it.key} is ${it.value}") }
forEach
다음에 매개변수가 포함된 괄호를 사용하는 대신 함수 이름 다음에 코드를 포함하는 중괄호 {}를 사용한 것을 볼 수 있다. map
이나 filter
도 마찬가지이다.
중괄호 안에 작은 함수를 작성하는 것과 같지만 함수 이름은 없다. 이렇게 이름이 없으며 곧바로 표현식으로 사용할 수 있는 함수는 아주 유용한 개념으로, 람다 표현식 또는 간략하게 람다라고 한다.
예를 들어 매개변수에 3을 곱해서 반환하는 람다 표현식을 정의하고, 이 함수를 변수 triple에 넣으면 위와 같다. (Int)->Int
는 함수의 타입을 정의하는데, 매개변수로 int가 들어가고 반환값이 int 라는 뜻이다.
fun main() {
val triple: (Int) -> Int = { a: Int -> a * 3 }
println(triple(5)) //실행결과: 15
}
매개변수의 명시적인 선언(a: Int
)을 생략하고 함수 화살표(->)를 생략하고 함수 본문만 사용할 수도 있다.
val triple: (Int) -> Int = { it * 3 }
고차함수는 함수(이 경우에는 람다)를 다른 함수로 전달하거나 다른 함수에서 함수를 반환하는 것을 의미한다. map
, filter
, forEach
함수는 모두 매개변수로 함수를 사용했으므로(프린트문, if문 등) 고차 함수의 예이다.
문자열 목록을 사전식 정렬하려면 내장된 sorted()
메서드를 컬렉션에 사용하면 된다. 그러나 문자열 길이를 기준으로 목록을 정렬하려면 두 문자열의 길이를 가져와 비교하는 코드를 작성해야 한다. Kotlin에서는 람다를 sortedWith()
메서드에 전달하여 작성할 수 있다.sortedWith()
역시 고차함수의 예이다.
println(peopleNames.sortedWith { str1: String, str2: String -> str1.length - str2.length })
string 2개가 매개변수로 들어가는 이유는, 두 객체의 길이를 비교하여 뭐가 더 짧은 이름인지 알기 위해서이다. 이렇게 해주면 sortedWith
은 필요한 모든 비교연산을 알아서 수행하고 이름 길이가 짧은 순으로 오름차순 정렬 해준다.
파이썬이면 peopleNames.sort(key=len)
하나면 끝날일을..떼잉..