데이터를 다루는 그릇인 컬렉션은 모든 애플리케이션의 뼈대입니다. 코틀린의 컬렉션 API는 자바보다 훨씬 강력하고 함수형 프로그래밍에 친화적입니다.
하지만 단순히 `listOf`만 쓰고 계신가요? 가변성(Mutability) 분리와 지연 계산(Lazy Evaluation)의 개념을 이해하지 못하면 코틀린 컬렉션의 진가를 발휘할 수 없습니다. 오늘은 코틀린 컬렉션의 핵심 철학과 성능 최적화 팁을 알아봅니다.

1. 코틀린 컬렉션의 대원칙: 불변(Immutable) vs 가변(Mutable)
코틀린은 컬렉션을 설계할 때부터 '읽기 전용'과 '수정 가능'을 인터페이스 레벨에서 분리했습니다. 이것이 자바와 가장 큰 차이점입니다.
- `List<T>`: 읽기만 가능 (`get`, `size` 등). `add`, `remove` 없음.
- `MutableList<T>`: 읽고 쓰기 가능 (`add`, `remove`, `clear` 등).
val readOnlyList: List<Int> = listOf(1, 2, 3)
// readOnlyList.add(4) // 컴파일 에러!
val mutableList: MutableList<Int> = mutableListOf(1, 2, 3)
mutableList.add(4) // OK
왜 이렇게 나눴을까요?
데이터의 변경 가능성을 명시적으로 제한함으로써, 예측 불가능한 사이드 이펙트를 줄이기 위함입니다. 함수 파라미터로 `List`를 받았다면, "이 함수는 리스트 내용을 바꾸지 않는다"는 것을 보장받을 수 있습니다.
2. Set과 Map: 중복과 검색의 미학
2-1. Set (집합)
순서가 중요하지 않고 중복을 허용하지 않는 자료구조입니다. '로또 번호 6개 생성' 같은 문제에서 `List`에 `contains`를 쓰며 중복을 검사하는 것은 하수입니다. `Set`을 쓰면 알아서 중복이 제거됩니다.
val lotto = mutableSetOf<Int>()
while (lotto.size < 6) {
lotto.add(Randoms.pickNumberInRange(1, 45))
}
// Set 구현체는 내부적으로 HashMap을 사용하여 검색 속도가 O(1)에 가깝습니다.
2-2. Map (키-값 쌍)
코틀린의 Map은 초기화가 매우 직관적입니다. `to` 중위 함수(Infix function)를 사용합니다.
val map = mapOf(
"Korea" to "Seoul",
"Japan" to "Tokyo",
"USA" to "Washington"
)
여기서 `"Korea" to "Seoul"`은 내부적으로 `Pair` 객체를 생성합니다.
3. 대용량 데이터 처리: Sequence (지연 계산)
이 부분은 많은 분들이 놓치지만 성능상 매우 중요합니다. 코틀린의 기본 컬렉션 연산(`map`, `filter` 등)은 Eager Evaluation(즉시 계산)을 수행합니다. 각 단계마다 새로운 중간 리스트를 생성한다는 뜻입니다.
val list = (1..1000000).toList()
// [비효율적]
// 1. map에서 100만 개짜리 새 리스트 생성
// 2. filter에서 또 새 리스트 생성
list.map { it * 2 }
.filter { it % 3 == 0 }
.first()
데이터가 아주 많을 때는 Sequence를 사용해야 합니다. 자바의 `Stream`과 유사하게 Lazy Evaluation(지연 계산)을 수행합니다.
// [효율적]
// 중간 리스트를 만들지 않고, 필요할 때(여기선 first 호출 시) 연산을 수행
list.asSequence()
.map { it * 2 }
.filter { it % 3 == 0 }
.first()
`Sequence`는 중간 결과를 저장하는 리스트를 만들지 않고, 파이프라인처럼 데이터를 하나씩 흘려보냅니다. 데이터가 많거나 연산 체인이 길 때 필수적입니다.
4. EnumMap: Map 중의 Map
만약 Map의 키(Key)가 Enum Class라면? 무조건 `EnumMap`을 써야 합니다. 일반 `HashMap`은 해시 충돌 가능성이 있고 해시 계산 비용이 들지만, `EnumMap`은 내부적으로 배열(Array)을 사용합니다. Enum의 `ordinal`(순서)을 인덱스로 사용하기 때문입니다.
// 성능 최적화된 로또 결과 저장소
val stats = EnumMap<LottoResult, Int>(LottoResult::class.java)
이것은 현존하는 Map 구현체 중 가장 빠르고 메모리를 적게 사용합니다.
5. 정리
- 가능하면 읽기 전용(`List`, `Set`)을 사용하여 불변성을 지키세요.
- 중복 제거가 필요하면 `List` 대신 `Set`을 쓰세요.
- 데이터가 수만 건 이상이거나 연산 단계가 많으면 `Sequence`로 변환하세요.
- 키가 Enum인 Map은 `EnumMap`이 정답입니다.
자료구조를 적재적소에 쓰는 능력, 그것이 곧 개발자의 실력입니다.
'Dev Log > Kotlin' 카테고리의 다른 글
| [우테코] 🏪편의점 미션: 흩어진 데이터를 하나의 객체로 모으는 기술 (feat. 프로모션/일반 재고 관리) (0) | 2026.01.06 |
|---|---|
| [Kotlin] static은 잊어라, Companion Object(동반 객체)의 모든 것 (1) | 2026.01.04 |
| [Kotlin] 문자열(String)을 지배하는 자: 템플릿부터 StringBuilder 성능 최적화까지 (0) | 2026.01.04 |
| [Kotlin] 클래스의 품격: data class와 enum class 내부 동작 원리부터 실무 활용까지 (0) | 2026.01.04 |
| [Kotlin] 함수를 변수처럼? 고차함수와 더블 콜론(::) 완벽 이해하기 (1) | 2026.01.04 |