1. 코틀린 스코프 함수 소개
- 스코프 함수(Scope functions)는 객체의 컨텍스트 내에서 코드 블록을 실행하기 위한 목적으로 존재하는 함수입니다.
- 이 함수들을 사용하면 객체의 이름을 반복하지 않고도 그 객체에 대해 여러 연산을 수행할 수 있습니다.
- 코틀린 표준 라이브러리는 다섯 가지 스코프 함수를 제공합니다:
with
,apply
,let
,also
,run
- 이 함수들은 모두 람다식을 인자로 받고, 객체를 컨텍스트로 하여 특정 스코프에서 코드 블록을 실행합니다.
2. 스코프 함수의 공통점과 차이점
2.1 스코프 함수의 공통점
- 모든 스코프 함수는 코드 블록을 실행하기 위한 임시 스코프를 형성합니다.
- 이 스코프 내에서는 객체 이름을 사용하지 않고도 객체에 접근할 수 있습니다.
- 스코프 함수를 사용하면 코드의 가독성과 간결성이 향상됩니다.
2.2 스코프 함수의 차이점
스코프 함수들은 다음 세 가지 특성에 따라 구분됩니다:
- 수신 객체 지정 방식:
- 수신 객체를 람다의 수신자(
this
)로 제공:with
,apply
,run
- 수신 객체를 람다의 인자(
it
)로 제공:let
,also
- 수신 객체를 람다의 수신자(
- 반환 값:
- 컨텍스트 객체 반환:
apply
,also
- 람다 결과 반환:
with
,let
,run
- 컨텍스트 객체 반환:
- 확장 함수 여부:
- 확장 함수로 호출:
apply
,let
,also
,run
- 일반 함수로 호출:
with
- 확장 함수로 호출:
다음 표는 이러한 차이점을 요약합니다:
함수 | 수신 객체 참조 | 반환 값 | 확장 함수 여부 |
---|---|---|---|
with | this | 람다 결과 | 일반 함수 |
apply | this | 컨텍스트 객체 | 확장 함수 |
run | this | 람다 결과 | 확장 함수 |
let | it | 람다 결과 | 확장 함수 |
also | it | 컨텍스트 객체 | 확장 함수 |
3. with 함수
with
는 객체의 이름을 반복하지 않고도 그 객체의 멤버에 접근할 수 있게 해주는 함수입니다.
3.1 with 함수의 구조
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
receiver
: 수신 객체block
: 수신 객체를 수신자(this
)로 갖는 람다 블록- 반환값: 람다 블록의 결과
3.2 with 사용 예시
기본 예시
// with 사용 전
val person = Person()
println(person.name)
println(person.age)
person.introduceYourself()
// with 사용 후
with(person) {
println(name) // person.name 대신
println(age) // person.age 대신
introduceYourself() // person.introduceYourself() 대신
}
StringBuilder로 문자열 만들기
fun alphabet(): String {
val stringBuilder = StringBuilder()
return with(stringBuilder) {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
toString()
}
}
더 간결하게 작성한 예시:
fun alphabet() = with(StringBuilder()) {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
toString()
}
3.3 with 사용 권장 상황
- 수신 객체를 변경하지 않고 멤버 함수를 여러 번 호출할 때
- 객체의 멤버에 대한 그룹 연산을 수행할 때
- 람다의 결과가 필요할 때
- 널이 아닌 객체에 대해 작업할 때
노트
with
는 확장 함수가 아니기 때문에 체이닝에 사용할 수 없습니다. 주로 지역 변수로 사용되는 객체에 적합합니다.
4. apply 함수
apply
함수는 객체 초기화나 빌더 패턴 구현에 특히 유용합니다.
4.1 apply 함수의 구조
inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
T
: 수신 객체 타입block
: 수신 객체를 수신자(this
)로 갖는 람다 블록- 반환값: 수신 객체 자체
4.2 apply 사용 예시
객체 초기화
val peter = Person().apply {
name = "Peter"
age = 23
email = "peter@example.com"
}
문자열 빌더 사용
fun alphabet() = StringBuilder().apply {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
}.toString()
뷰 설정(안드로이드)
val textView = TextView(context).apply {
text = "Sample Text"
textSize = 20.0f
setPadding(10, 0, 0, 0)
setTextColor(Color.BLACK)
}
4.3 apply 사용 권장 상황
- 객체 초기화 시 프로퍼티를 설정할 때
- 빌더 패턴을 대체할 때
- 수신 객체 자신을 다시 반환해야 할 때
- 코드 블록 내에서 수신 객체를 변경해야 할 때
5. let 함수
let
함수는 nullable 객체를 처리하거나 지역 변수의 범위를 제한할 때 유용합니다.
5.1 let 함수의 구조
inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
T
: 수신 객체 타입block
: 수신 객체를 인자(it
)로 갖는 람다 블록- 반환값: 람다 블록의 결과
5.2 let 사용 예시
널 체크 후 코드 실행
val nullableName: String? = getNullableName()
// let 사용 전
if (nullableName != null) {
println("Name length: ${nullableName.length}")
}
// let 사용 후
nullableName?.let {
println("Name length: ${it.length}")
}
지역 변수의 범위 제한
val numbers = listOf("one", "two", "three", "four")
val modifiedList = numbers.map { it.uppercase() }.filter { it.length > 3 }.let {
println("Elements count: ${it.size}")
it.sorted()
}
타입 변환과 동시에 연산 수행
val str = "Hello"
val result = str.let {
val firstChar = it.first().uppercase()
val restChars = it.substring(1)
firstChar + restChars
}
5.3 let 사용 권장 상황
- 널이 아닌 객체에 대해서만 코드 블록 실행할 때 (안전 호출 연산자
?.
와 함께 사용) - 지역 변수의 범위를 제한하고 싶을 때
- 하나의 표현식으로 여러 연산을 수행할 때
- 객체를 다른 타입으로 변환하고 결과를 사용할 때
6. also 함수
also
함수는 객체의 프로퍼티나 함수를 사용하는 것보다 객체 자체를 참조해야 할 때 유용합니다.
6.1 also 함수의 구조
inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
T
: 수신 객체 타입block
: 수신 객체를 인자(it
)로 갖는 람다 블록- 반환값: 수신 객체 자체
6.2 also 사용 예시
로깅이나 디버깅
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list before adding elements: $it") }
.add("four")
객체 유효성 검사
class Book(author: Person) {
val author = author.also {
requireNotNull(it.age)
println("Author's name: ${it.name}")
}
}