Kotlin 코딩테스트 문법 시리즈 ⑬ : Scope Function 정리 (let, also, apply, run, takeIf)

2025. 7. 13. 04:55·Code Odyssey

✅ Kotlin에는 객체를 다루는 데 유용한 Scope Function (스코프 함수)들이 존재합니다. 대부분 람다와 함께 쓰이며, null-safe, 객체 구성, 사이드 이펙트 처리, 조건부 반환 등에 사용됩니다. 컬렉션과 직접 관련되기보다는 객체 흐름을 다루는 데 적합합니다.


🔎 Scope Function 전체 개념 정리

스코프 함수(Scope Function)는 특정 객체를 임시로 블록 안으로 옮겨와 속성을 수정하거나, 값을 계산하거나, 로깅 등 부수 효과를 주는 작업을 간결하게 수행할 수 있도록 해줍니다. 이 함수들은 주로 함수형 스타일, null-safe 처리, 초기화 및 체이닝 처리에 유리합니다.

Scope 함수는 특정 상황에 맞게 사용하면 코드의 가독성과 효율을 높일 수 있습니다.

수신 키워드란? 스코프 함수의 블록 내부에서 수신 객체(호출 주체)를 참조할 때 사용하는 이름입니다. it은 기본 이름이며, this는 현재 객체 자체를 직접 가리킵니다. apply, run처럼 객체 내부 프로퍼티나 메서드를 자주 다루는 함수는 this를 통해 더 자연스럽게 접근할 수 있어 DSL 스타일 구현에 유리합니다.

함수 반환값 수신 키워드 쓰면 좋은 상황 조건 실행 여부
let 마지막 식 it null-safe 처리, 값 임시 조작, 결과 계산 ?.let {}로 사용하면 null 아닐 때만 실행됨
also 원본 객체 it 로깅, 디버깅, 중간 확인 (side effect) 항상 실행됨
apply 원본 객체 this 객체 생성 후 필드값 일괄 설정 항상 실행됨
run 마지막 식 this 블록 실행 후 결과 계산, DSL 스타일 항상 실행됨
takeIf 원본 or null it 조건에 따라 객체 유지 or 제거 조건이 true일 때만 값 유지됨

🔹 let — 값 임시 처리 및 결과 반환

val name: String? = "Alice"
val length = name?.let {
    println("Hello, $it")
    println("Length: ${it.length}")
    it.length
} ?: 0
println(length) // 출력: 5

// 🔸 null-safe가 아닌 let도 가능
val s: String? = null
s.let {
    println("let 내부 실행됨. 값: $it")  // 출력: let 내부 실행됨. 값: null
}
  • ?.let {}는 null 아닐 때만 실행
  • 내부에서는 it으로 참조, 마지막 표현식이 결과로 반환됨
  • null-safe 연산을 안전하게 처리할 때 적합

🔹 also — 부수 효과 (side effect), 원본 유지

val list = mutableListOf("A", "B")
val result = list.also {
    println("Before add: $it")
}.apply { add("C") }
println(result) // 출력: [A, B, C]
  • 디버깅, 로깅 등 부수 효과용
  • it으로 참조하며, 원본을 그대로 반환함
  • 메서드 체이닝 중간 확인이나 추적에 적합

🔹 apply — 객체 초기화 블록

data class User(var name: String, var age: Int)

val user = User("", 0).apply {
    name = "Alice"
    age = 25
}
println(user)  // 출력: User(name=Alice, age=25)
  • this로 속성 설정
  • 객체 자신을 그대로 반환
  • 생성자 이후 필드를 일괄 설정할 때 적합 (Builder 스타일)

🔹 run — 블록 실행 후 결과 반환

아래는 run과 let의 차이를 명확히 비교한 표입니다:

항목 let run
수신 객체 접근 it (명시적으로 변수 이름 지정 가능) this (생략 가능, 멤버·함수 직접 접근 가능)
리턴값 람다의 마지막 표현식 결과 → 보통 가공된 값이나 결과 값 람다의 마지막 표현식 결과 → 보통 계산 결과 or 객체 자체
주 사용 목적 ▸ null-safe 처리 (?.let) ▸ 임시 변수 가공 ▸ 함수 체이닝 중 중간 처리 ▸ 객체 초기화/구성 ▸ 계산 로직 블록화 ▸ DSL 스타일 작성
대표 사용 예 nullableVar?.let { doSomething(it) } value.trim().let { ... } User().run { name = "A"; this } listOf(1,2,3).run { sum() }
가독성 포인트 it을 사용 → 코드가 짧고 명확하지만 반복되면 불편할 수 있음 this 기반 → 객체의 속성이나 함수 접근이 편리해 가독성↑
null-safe 대응 매우 적합 (?.let { ... }) 가능은 하지만 일반적이지 않음 (?.run { ... })
대표 리턴 용도 새로운 값 (계산된 결과 등) 반환 객체 또는 새로운 값(계산된 결과 등) 반환

스코프 함수 중 run은 임시 객체에서 계산을 수행하고 결과만 반환하고자 할 때 가장 직관적입니다. 또한 this로 수신 객체의 속성에 바로 접근할 수 있어, DSL 구성 또는 계산 전용 블록에 적합합니다.

🔸 let과의 차이: run이 적합한 경우

  • 불필요한 변수명(it) 없이 this로 내부 필드에 바로 접근 가능
  • 객체를 임시로 사용해 결과만 추출하거나, DSL처럼 설정 블록을 구성할 때 적합
  • 객체를 반환하지 않고 계산 결과만 원할 때 간결함

✅ 예시 1: 계산만 하고 결과 반환 (임시 객체)

val total = listOf(1, 2, 3).run {
    println("총합 계산 중...")
    sum()
}
println(total)  // 출력: 총합 계산 중...
6

✅ 예시 2: 객체 구성 후 자기 자신 반환

data class User(var name: String = "", var age: Int = 0)

val user = User().run {
    name = "Arum"
    age = 28
    this // apply와 달리 마지막에 return을 명시해야함
}
println(user.name)  // 출력: Arum
  • run은 this로 객체 내부 필드에 직접 접근 가능
  • let은 it을 명시적으로 사용 (외부 변수와 충돌 방지)
  • 블록 실행 후 결과만 필요할 때 간결하게 사용 가능

🔹 takeIf — 조건부 반환 (조건 false면 null)

val score = 85
val result = score.takeIf { it >= 80 }?.let { "합격 ($it)" } ?: "불합격"
println(result)  // 출력: 합격 (85)
  • 조건 만족 시 값 유지, 아니면 null
  • null-safe 체이닝이 가능함
  • 조건부 필터링 또는 조기 탈락 처리에 유리

🧩 실전 예시 (체이닝 활용)

📌 Scope 함수는 입력으로 리스트, 맵 등 다중 값도 가능하며, 출력 역시 단일 값뿐 아니라 리스트, 맵 등 컬렉션 형태로도 반환될 수 있습니다.

✅ 합격/불합격 여부 판별 with takeIf

val score = 85
val result = score.takeIf { it >= 80 }?.let { "합격 ($it)" } ?: "불합격"
println(result)  // 출력: 합격 (85)

✅ 리스트 필터링 후 가공 (다중값 처리)

val doubled: List<Int>? = listOf(1, 2, 3).takeIf { it.isNotEmpty() }?.map { it * 2 }
println(doubled)  // 출력: [2, 4, 6]

✅ 복합 처리 체이닝 예시

val result = listOf(1, 2, 3, -1)
    .filter { it > 0 }
    .map { it * 2 }
    .takeIf { it.isNotEmpty() }
    ?.let { it.sum() }
    ?: 0  // 결과: 12

다음은 시리즈 ⑭: 함수 패턴 (Currying, 함수 참조, 재귀 등) 으로 이어집니다.

'Code Odyssey' 카테고리의 다른 글

Kotlin 코딩테스트 문법 시리즈 ⑮ : 람다, 수신 함수, 클로저 개념과 활용  (0) 2025.07.15
Kotlin 코딩테스트 문법 시리즈 ⑭ : 함수 패턴 정리  (3) 2025.07.14
Kotlin 코딩테스트 문법 시리즈 ⑫ : 누적 함수 정리 (reduce, fold, scan)  (1) 2025.07.12
Kotlin 코딩테스트 문법 시리즈 ⑪ : 컬렉션 유틸 함수 정리 (zip, chunked, windowed 등)  (4) 2025.07.11
Kotlin 코딩테스트 문법 시리즈 ⑩ : 고차 함수 정리 (map, filter, any 등)  (1) 2025.07.10
'Code Odyssey' 카테고리의 다른 글
  • Kotlin 코딩테스트 문법 시리즈 ⑮ : 람다, 수신 함수, 클로저 개념과 활용
  • Kotlin 코딩테스트 문법 시리즈 ⑭ : 함수 패턴 정리
  • Kotlin 코딩테스트 문법 시리즈 ⑫ : 누적 함수 정리 (reduce, fold, scan)
  • Kotlin 코딩테스트 문법 시리즈 ⑪ : 컬렉션 유틸 함수 정리 (zip, chunked, windowed 등)
Celion
Celion
오늘도 평소처럼 화이팅!
  • Celion
    Orion Log
    Celion
  • 전체
    오늘
    어제
    • 전체 글 (144)
      • Uncompiled Thoughts (8)
        • 네이버 부스트캠프 10기 (5)
      • CS 기초부터 한 걸음씩 (34)
      • Code Odyssey (22)
      • Algorithm (77)
        • Coding Test Records (63)
      • Git (3)
      • reference (0)
  • 블로그 메뉴

    • 태그
    • 방명록
  • 태그

    문법정리
    알고리즘고득점kit
    프로그래머스
    Kotlin
    백준
    Level3
    greedy
    시뮬레이션
    java
    Level2
    boostcamp
    코테
  • 최근 글

  • 인기 글

  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.4
Celion
Kotlin 코딩테스트 문법 시리즈 ⑬ : Scope Function 정리 (let, also, apply, run, takeIf)
상단으로

티스토리툴바