본문 바로가기
개발&프로그래밍

[Kotlin] Kotlin mapNotNull 완전 정복 | 리스트 변환부터 실무 활용까지

by 재아군 2025. 6. 18.

Kotlin mapNotNull 완전 정복 | 리스트 변환부터 실무 활용까지

Kotlin을 사용하면서 가장 자주 마주치는 기능 중 하나가 바로 mapNotNull입니다.

특히, 리스트를 필터링하고 변환할 때 정말 유용하게 사용할 수 있는데요.

이 글에서는 mapNotNull의 기본 개념부터 다양한 실무 활용 예제까지 쉽게 풀어보겠습니다.

 

mapNotNull이란?

mapNotNull은 Kotlin 컬렉션에서 각 요소에 변환(transform) 함수를 적용한 뒤, null이 아닌 결과만 모아서 새로운 리스트로 반환하는 함수입니다. 간단한 예제를 볼까요?

val numbers = listOf(1, 2, 3, 4, 5, 6)
val doubled = numbers.mapNotNull { if (it % 2 == 0) it * 2 else null }
println(doubled) // [4, 8, 12]

 

짝수만 두 배로 변환하고, 나머지 홀수는 제외되었습니다. 이렇게 null을 걸러내는 처리를 깔끔하게 할 수 있습니다.

즉, 데이터를 변환하면서 동시에 null 값을 필터링하고 싶은 경우 매우 효율적인 도구입니다. map()filterNotNull()을 각각 사용하는 것보다 더 간단하고 직관적인 코드 작성을 가능하게 해줍니다.

 

map + filterNotNull()과의 차이점

mapNotNull은 다음의 코드를 단축시킨 형태입니다:

val result = list.map { it.toIntOrNull() }.filterNotNull()

위의 코드와 동일한 결과를 mapNotNull로 더 간단히 쓸 수 있죠.

 

val result = list.mapNotNull { it.toIntOrNull() }

가독성과 성능 면에서 모두 유리합니다. 특히, 중간 컬렉션이 생기지 않기 때문에 성능상으로도 이점이 있습니다.

 

다양한 컬렉션에서의 활용

 

List에서 사용하기

문자열 리스트에서 정수로 변환 가능한 값만 필터링하는 예제입니다:

val strings = listOf("12a", "45", "", "3")
val ints = strings.mapNotNull { it.toIntOrNull() }
println(ints) // [45, 3]

toIntOrNull()은 변환 실패 시 null을 반환하므로, mapNotNull로 처리하면 실패한 항목은 자동 제거됩니다.

 

Map에서 사용하기

Map의 각 항목을 조건에 따라 필터링해 리스트로 추출하는 예제입니다:

val map = mapOf("Alice" to 20, "Tom" to 13, "Bob" to 18)
val adults = map.mapNotNull { (name, age) -> name.takeIf { age >= 18 } }
println(adults) // [Alice, Bob]

takeIf는 조건이 true일 때 해당 값을 반환하고, false일 때 null을 반환합니다. 이와 mapNotNull을 함께 쓰면 매우 유용하죠.

 

Sequence에서 사용하기

Sequence는 lazy evaluation을 지원하여 큰 데이터셋 처리에 유리합니다:

val sequence = sequenceOf("1", "two", "3")
val validInts = sequence.mapNotNull { it.toIntOrNull() }.toList()
println(validInts) // [1, 3]

 

Flow(Coroutines)에서도 사용 가능

Flow에서도 동일한 방식으로 활용할 수 있으며, null을 안전하게 필터링하면서 변환이 가능합니다:

flowOf("10", "20", "x")
    .mapNotNull { it.toIntOrNull() }
    .collect { println(it) } // 10, 20

 

실무 팁: 이렇게 쓰면 좋아요

 

takeIf와 함께 조건 필터링

val numbers = listOf(1, 2, 3, 4, 5)
val filtered = numbers.mapNotNull { it.takeIf { it > 3 } }
println(filtered) // [4, 5]

takeIf는 조건에 맞는 값만 남기고, 아닌 경우 null을 반환하므로 mapNotNull과 궁합이 잘 맞습니다.

 

null 가능성 있는 람다 처리

val inputs = listOf("kotlin", null, "mapNotNull")
val upper = inputs.mapNotNull { it?.uppercase() }
println(upper) // [KOTLIN, MAPNOTNULL]

null-safe 연산자(?.)와 함께 쓰면, null을 걸러내면서도 예외 없이 안전하게 처리할 수 있습니다.

 

주의사항과 성능 이슈

  • mapNotNull은 중간 컬렉션을 줄여주기 때문에 메모리 효율이 좋습니다.
  • 하지만 map()filterNotNull()을 조합해도 충분할 경우, 코드 가독성과 의미가 더 잘 드러날 수 있습니다.
  • Sequence나 Flow와 함께 사용하면 더욱 Lazy하게 작동합니다. 이는 대량의 데이터를 처리할 때 매우 유리합니다.

 

요약

항목 설명
목적 변환과 필터링을 한 번에 수행
대상 List, Map, Sequence, Flow 등 거의 모든 컬렉션
장점 null 자동 제거, 가독성, 성능 이점
주의 상황에 따라 map + filterNotNull 조합이 더 명확할 수 있음

 

mapNotNull은 단순한 함수처럼 보이지만, 쓰임새가 정말 다양하고 강력한 도구입니다.

이 글을 통해 여러분도 실무에서 더 유용하게 활용하실 수 있기를 바랍니다.

댓글