안드로이드 코틀린 언어 학습 노트 목차

컬렉션·시퀀스 — 함수형 연산·지연 평가

코틀린 컬렉션은 함수형 연산의 보고다 — map·filter·groupBy 같은 수십 개의 표준 연산으로 반복문 없이 데이터를 변환한다. 또 읽기 전용/가변을 타입으로 구분하고, *큰 데이터엔 시퀀스(지연 평가)*로 성능을 챙긴다. 안드로이드에서 서버 응답을 화면 모델로 변환하는 코드 대부분이 이 연산들이다. (출처: Kotlin Docs — Collections / Kotlin in Action 2e Ch 6.)

읽기 전용 vs 가변 — 타입으로 구분

코틀린은 *읽기 전용 인터페이스(List)*와 *가변 인터페이스(MutableList)*를 나눈다.

val ro: List<Int> = listOf(1, 2, 3)          // 읽기 전용 (add/remove 없음)
val mut: MutableList<Int> = mutableListOf(1) // 가변
mut.add(2)

val set = setOf("a", "b")                    // Set (중복 없음)
val map = mapOf("k" to 1, "j" to 2)          // Map (to로 쌍 생성)
map["k"]                                       // 1

원칙은 **"밖으로는 읽기 전용을 노출"**한다 — 내부는 MutableList로 다루되 getter는 List 내보내 외부 수정을 막는다(캡슐화). val list: List<X> get() = _list.

함수형 연산 — 반복문을 대체한다

가장 많이 쓰는 변환·집계 연산들:

val members = listOf(
    Member("Kim", 30), Member("Lee", 17), Member("Park", 25))

members.filter { it.age >= 20 }              // 성인만
       .map { it.name }                       // 이름만 → ["Kim","Park"]
       .sorted()                              // 정렬

members.groupBy { it.age >= 20 }             // Map<Boolean, List<Member>>
members.associateBy { it.name }              // Map<name, Member>
members.sumOf { it.age }                     // 합계 72
members.maxByOrNull { it.age }               // 최고령
members.partition { it.age >= 20 }           // (성인들, 미성년들)
members.flatMap { it.tags }                  // 중첩 리스트 평탄화
members.any { it.age < 20 }                  // 하나라도? → true
members.all { it.age > 0 }                   // 전부? → true
members.count { it.age >= 20 }               // 조건 개수

fold/reduce누적이다 — list.fold(0) { acc, x -> acc + x.age }. 람다에서 구조 분해도 된다 — map.forEach { (key, value) -> ... }.

즉시(eager) vs 지연(lazy) — 시퀀스

기본 컬렉션 연산은 즉시 평가(eager)각 단계마다 새 중간 리스트를 만든다. 작은 데이터엔 괜찮지만, 큰 데이터에 연산을 길게 체이닝하면 중간 리스트가 메모리·시간을 잡아먹는다.

// 즉시: filter가 100만짜리 리스트를 새로 만들고, map이 또 새 리스트를 만듦
val r1 = bigList.filter { it.active }.map { it.name }.first()

**시퀀스(Sequence)**는 지연 평가(lazy)원소 하나가 전체 체인을 통과하고, 최종 연산(terminal)이 호출될 때만 흐른다. 중간 리스트가 없고, first()처럼 일찍 멈출 수 있으면 나머지를 아예 처리 안 한다(단락 평가).

// 지연: 원소를 하나씩 filter→map 통과, first() 만나면 즉시 종료
val r2 = bigList.asSequence()
    .filter { it.active }
    .map { it.name }
    .first()                 // 첫 매칭 찾으면 끝 — 100만 다 안 봄
다이어그램 로딩 중…

언제 시퀀스? — 원소가 많고(수천~), 연산을 여러 단계 체이닝하거나, 일찍 멈출 수 있을 때. 작은 컬렉션엔 오히려 일반 연산이 간단·빠르다(시퀀스는 호출 오버헤드가 있음). generateSequence { ... }무한 시퀀스도 만든다(피보나치 등).

안드로이드 관용구

// 서버 DTO 리스트 → UI 모델로 변환 (전형적)
val uiItems = response.items
    .filter { it.visible }
    .sortedByDescending { it.updatedAt }
    .map { it.toUiModel() }

// nullable 원소 제거
val names: List<String> = rawList.mapNotNull { it.name }   // null인 건 빼고 모음

정리 — 변환은 연산으로, 큰 데이터는 시퀀스로

컬렉션의 핵심 — 읽기 전용/가변을 타입으로 구분(밖엔 읽기 전용), map·filter·groupBy 등 함수형 연산으로 반복문을 대체, 큰 데이터·긴 체인·조기 종료엔 asSequence()로 지연 평가. 서버 응답을 화면 모델로 빚는 안드로이드 코드가 전부 이 연산들의 조합이다.

한 줄 요약List(읽기)/MutableList(가변), 밖엔 읽기 전용 노출. map/filter/groupBy/associateBy/flatMap/fold/partition로 반복문 대체. 기본은 즉시(중간 리스트), 크고 긴 체인·조기 종료엔 asSequence() 지연 평가. null은 mapNotNull.

(출처: Kotlin Docs — Collections / Sequences / Kotlin in Action 2e Ch 6 / Effective Kotlin Item 49.)

스코프 함수·위임 — let·run·apply·also·by·lazy코루틴 — suspend·구조적 동시성·Dispatchers·취소