Kotlin Collection과 Scope Functions

한 줄 정의

Kotlin collection 함수와 scope function은 데이터를 변환하고 객체 초기화나 null 처리를 간결하게 표현하는 도구다.

실무에서 왜 중요한가

Kotlin은 컬렉션 처리와 객체 조립 코드가 짧다. 하지만 남용하면 다음 문제가 생긴다.

  • map, filter, groupBy 체인이 길어져 디버깅이 어렵다.
  • also, apply, let, run을 섞어 itthis가 헷갈린다.
  • 큰 컬렉션에서 중간 컬렉션을 많이 만들어 메모리 사용량이 늘어난다.
  • Java Stream과 Kotlin collection의 eager/lazy 차이를 모르고 성능을 오해한다.
  • scope function 안에서 side effect를 숨긴다.

Collection 처리

Kotlin collection 함수는 기본적으로 eager evaluation이다.

val userIds = orders
    .filter { it.status == OrderStatus.PAID }
    .map { it.userId }
    .distinct()

각 단계가 새 컬렉션을 만들 수 있다. 데이터가 크거나 체인이 길면 Sequence를 검토한다.

val userIds = orders.asSequence()
    .filter { it.status == OrderStatus.PAID }
    .map { it.userId }
    .distinct()
    .toList()

단, 작은 목록이나 단순 변환에서는 일반 collection 함수가 더 읽기 쉽다.

자주 쓰는 패턴

목적함수
변환map
조건 필터filter
null 제거와 변환mapNotNull
key 기준 변환associateBy
그룹핑groupBy
합계/집계sumOf, fold
val userMap = users.associateBy { it.id }
 
val responses = orders.map { order ->
    OrderResponse.of(order, userMap[order.userId])
}

Java의 stream().collect(toMap())보다 간결하지만, key 중복 가능성은 여전히 확인해야 한다.

Scope Functions

함수반환컨텍스트주 사용처
letlambda 결과itnull 처리, 변환
also원본 객체it로깅, 부가 작업
apply원본 객체this객체 설정
runlambda 결과this객체 기반 계산
withlambda 결과this이미 있는 객체 사용
val result = user
    ?.let { UserResponse.from(it) }
    ?: UserResponse.empty()

scope function은 짧은 블록에서만 읽기 좋다. 중첩되면 일반 변수로 풀어 쓰는 편이 낫다.

자주 나는 실수

  • let, also, apply를 중첩해서 현재 객체가 무엇인지 헷갈리게 만든다.
  • also 안에 중요한 비즈니스 로직을 숨긴다.
  • 큰 컬렉션에서 eager collection chain을 과하게 사용한다.
  • groupBy로 큰 map/list를 만들고 메모리 사용량을 고려하지 않는다.
  • associateBy에서 중복 key가 생기는 상황을 확인하지 않는다.

실무 판단 기준

상황권장
단순 목록 변환collection 함수
큰 데이터 연속 변환Sequence 검토
null이면 변환let
객체 초기 설정apply
로깅 같은 부가 작업also
복잡한 비즈니스 로직scope function보다 명시적 변수

확인 방법

  • scope function 중첩이 2단계 이상이면 풀어 쓰는 것을 검토한다.
  • 컬렉션 크기가 커질 수 있으면 중간 컬렉션 생성 비용을 확인한다.
  • associateBy, groupBy의 key 중복과 메모리 사용량을 확인한다.
  • 변환 과정에 side effect가 섞여 있는지 확인한다.

핵심 요약

Kotlin collection 함수는 데이터 변환과 조립을 간결하게 만든다.

하지만 기본 collection chain은 eager evaluation이라 큰 데이터에서는 중간 컬렉션 비용을 고려해야 한다.

scope function은 null 처리, 객체 설정, 부가 작업을 간결하게 만들지만 중첩하면 가독성이 급격히 떨어진다.

let, also, apply의 반환값과 컨텍스트 객체 차이를 알고 사용해야 한다.

실무에서는 짧고 명확한 변환에는 Kotlin 스타일을 쓰고, 복잡한 로직은 명시적 변수와 함수로 분리하는 것이 좋다.

꼬리 질문

점검 퀴즈

아래 문항은 개념을 실제로 설명할 수 있는지 점검하기 위한 것이다. 선택지를 누르면 정답 여부와 이유가 표시된다.

객관식Collection 함수와 Sequence의 실무적 차이로 가장 적절한 것은?

OXscope function을 중첩해서 많이 쓰면 this/it의 의미가 흐려져 가독성이 나빠질 수 있다.

객관식also와 apply의 차이를 설명할 때 핵심 기준은?

관련 문서