Kotlin 뿐만 아니라 여러 프로그래밍 언어에서 Collection(컬렉션)이라는 개념이 존재한다. 컬렉션이란 여러 개의 아이템을 그룹화한 데이터 타입을 말한다.
Kotlin의 컬렉션 타입은 두 가지 타입으로 나뉜다.
1. Read-only Collection : 읽기 전용 컬렉션으로 요소에 접근만 가능하다.
2. Mutable Collection : 요소 추가, 제거, 업데이트 등의 write 작업을 수행할 수 있는 변경 가능한 컬렉션
mutable 컬렉션을 반드시 var로 선언할 필요는 없다. val로 선언하더라도 write 작업은 여전히 가능하다. val로 선언하여 컬렉션에 대한 참조가 수정되는 것을 방지할 수 있으므로, var보다는 val로 선언하도록 하자.
fun main() {
val numbers = mutableListOf("one", "two", "three", "four")
numbers.add("five")
println(numbers) // 출력 : [one, two, three, four, five]
// numbers = mutableListOf("six", "seven") // 컴파일 오류 발생
}
위와 같이 numbers mutable 리스트에 추가는 가능하지만 새로운 mutable 리스트를 할당하는 것은 불가능하다.
open class Shape
class Rectangle: Shape()
class Circle: Shape()
fun main() {
val rectangles: List<Rectangle> = listOf(Rectangle(), Rectangle())
val shapes: List<Shape> = rectangles
}
위와 같이 Rectangle 클래스가 Shape 클래스를 상속받는다면, List<Rectangle>을 List<Shape>이 필요한 곳에서 사용할 수 있다. 즉 컬렉션 타입은 요소 타입과 동일한 상속 관계를 가진다. 단, Map의 경우 Value의 타입에 대해서는 공변성이 적용되지만 Key의 타입에 대해서는 적용되지 않는다.
open class Shape
class Rectangle : Shape()
class Circle : Shape()
fun main() {
val rectangles: MutableList<Rectangle> = mutableListOf(Rectangle(), Rectangle())
// val shapes: MutableList<Shape> = rectangles // 컴파일 오류
// shapes.add(Circle()) // 만약 허용된다면 Rectangle 타입 파라미터 위반
}
반면, Mutable 컬렉션은 공변성을 가지지 않는다. 위와 같이 MutableList<Rectangle>를 MutableList<Shape>으로 업캐스팅을 시도하면 Type mismatch로 컴파일 오류가 발생한다. 만약 공변성을 가지게 된다면 Rectangle 타입으로 제한된 MutableList에 Circle 객체를 추가하는 것이 가능해진다. 그래서 공변성을 가지지 않는 것이다.
public interface Set<out E> : Collection<E> { ... }
public interface MutableSet<E> : Set<E>, MutableCollection<E> { ... }
컬렉션 중 하나인 Set이 선언된 곳을 보면 읽기 전용 Set은 공변성 키워드인 out을 사용했지만 MutableSet은 out을 사용하지 않은 것을 볼 수 있다.
Kotlin의 대표적인 컬렉션 타입에는 List, Set, Map이 존재한다. 이 글에서는 읽기 전용 List, Set, Map만 다룬다.
public interface List<out E> : Collection<E> { ... }
List는 인덱스를 통해 요소에 접근할 수 있는 순서가 있는 컬렉션이다. 인덱스는 고유하지만 요소들은 중복된 값이 있을 수 있다.
fun main() {
val telephoneNumber = listOf(0, 2, 1, 2, 3, 4, 4, 3, 2, 1)
println(telephoneNumber.joinToString("")) // 출력 : 0212344321
}
예를 들면 전화번호는 숫자들의 그룹이며 그 순서가 중요하고 숫자는 중복될 수 있다.
data class Person(var name: String, var age: Int)
fun main() {
val bob = Person("Bob", 31)
val people = listOf(Person("Adam", 20), bob, bob)
val people2 = listOf(Person("Adam", 20), Person("Bob", 31), bob)
println(people == people2) // 출력 : true
}
그리고 중복된 값이 있을 수도 있다는 말에 대해 추가적인 예시로, 위와 같이 bob 인스턴스를 중복으로 리스트의 요소로 사용할 수 있다. 그리고 두 개 리스트의 크기가 같고 순서도 같으며 모든 요소가 동등성을 만족한다면 두 개 리스트의 동등성도 만족한다. 예시에서도 people[1]과 people2[1]은 다른 객체이지만 동등하기 때문에 두 개의 리스트는 동등하게 취급된다.
public interface Set<out E> : Collection<E> { ... }
Set은 순서가 정의되지 않은 고유한 요소들로 구성된 컬렉션이다. 고유한 요소들로 구성되어 있다는 뜻은 중복되는 요소가 없는 컬렉션이라는 의미이다. Set은 null을 요소로도 가질 수 있는데 null도 고유한 요소여야 하므로, Set은 오직 하나의 null만 가질 수 있다.
fun main() {
val lottoNumber = setOf(21, 42, 44, 1, 38, 12, 33)
println(lottoNumber) // 출력 : [21, 42, 44, 1, 38, 12, 33]
}
예를 들면 로또 번호는 중복되지 않는 고유한 숫자들로 구성되어 있지만 순서는 중요하지 않다.
fun main() {
val numbers = setOf(1, 2, 3, 4)
println("Number of elements: ${numbers.size}")
if (numbers.contains(1)) println("1 is in the set")
val numbersBackwards = setOf(4, 3, 2, 1)
println("The sets are equal: ${numbers == numbersBackwards}")
}
// 출력
// Number of elements: 4
// 1 is in the set
// The sets are equal: true
그리고 두 개 Set의 크기가 같고 같은 요소들을 가지고 있으면 두 Set은 동등한 것으로 간주된다. 위의 예시에서 setOf(1, 2, 3, 4), setOf(4, 3, 2, 1)으로 작성한 숫자의 순서는 다르지만, Set은 순서가 정의되지 않는 컬렉션이기 때문에 두 Set은 동등하다.
public interface Map<K, out V> { ... }
Map은 List, Set과 다르게 Collection interface를 상속받지 않는다. 하지만 공식 문서에서는 Kotlin의 컬렉션 중 하나로 분류하고 있다.
Map은 키-값 쌍으로 이루어진 컬렉션으로 키는 고유하며 각각 정확히 하나의 값과 매핑된다. 키는 중복을 허용하지 않지만 값은 중복을 허용한다.
fun main() {
val idNameMap = mapOf(1 to "John", 12 to "Michael", 31 to "Alice")
println(idNameMap) // 출력 : {1=John, 12=Michael, 31=Alice}
}
예를 들면 직원의 ID와 그들의 이름을 Map으로 표현할 수 있다. 직원의 ID는 고유하지만 이름은 동명이인이 존재할 수 있다.
fun main() {
val pair = "key1" to 1
val numbersMap = mapOf(pair, "key2" to 2, "key3" to 3, "key4" to 1)
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)
println("The maps are equal: ${numbersMap == anotherMap}")
}
두 개 Map이 같은 크기에 같은 요소를 가지고 있으면 동등한 것으로 간주한다. 순서는 상관없다.