✅ 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 |