티스토리 뷰

반응형

apply, with, let, also, run의 차이는 무엇일까?

Kotlin에서는 객체를 다루기 위한 다양한 함수들이 제공됩니다. 이 중 가장 흔히 사용되는 것이 apply, with, let, also, run 함수입니다. 이들 함수는 모두 객체를 인자로 받아 람다식을 실행하고, 그 결과를 반환합니다. 하지만, 이들 함수는 서로 다른 용도와 사용 방식을 가지고 있으므로, 각 함수들의 차이점을 살펴보도록 하겠습니다.

 

*구현체에서 contract { ... } 부분은 범위를 넘어서기 때문에 생략합니다.

 

apply 함수

apply 함수는 수신 객체를 인자로 받아 람다식을 실행하고, 수신 객체 자체를 반환합니다. 주로 객체를 생성하고 그 객체의 프로퍼티를 초기화하는 데 사용됩니다.

 

apply 함수의 구현체는 다음과 같습니다.

public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

apply 함수는 확장 함수로 정의되어 있으며, 제네릭 타입 T를 인자로 받습니다. 람다식인 block은 T 타입을 수신 객체로 받으며, 반환값이 없습니다. 또한 apply 함수는 block 람다식을 수신 객체로 실행한 뒤, 해당 수신 객체를 반환합니다. 이를 통해, 수신 객체의 초기화와 반환을 한번에 처리할 수 있습니다.

 

예제는 다음과 같습니다.

val person = Person().apply {
    name = "John"
    age = 30
    address = "Seoul, Korea"
}
 

위 예제에서 Person 객체를 생성하고, apply 함수를 사용하여 초기화합니다. 이때 apply 함수는 Person 객체를 반환하므로, person 변수에는 초기화된 Person 객체가 할당됩니다.

 

with 함수

with 함수는 수신 객체와 람다식을 인자로 받아 람다식을 실행하고, 람다식의 결과를 반환합니다. with 함수는 객체의 메서드를 호출할 때 자주 사용됩니다.

 

with 함수의 구현체는 다음과 같습니다.

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    return receiver.block()
}

with 함수는 확장 함수로 정의되어 있으며, 제네릭 타입 T와 R을 인자로 받습니다. block 람다식은 T 타입을 수신 객체로 받고, R 타입을 반환하며, with 함수는 receiver 인자로 전달된 수신 객체에 대해 block 람다식을 실행한 뒤, 해당 결과를 반환합니다. 이를 통해, 수신 객체의 메서드를 호출하면서도 반환값을 처리할 수 있습니다.

 

이에 대한 예제는 아래와 같습니다.

val person = Person()
with(person) {
    name = "John"
    age = 30
    address = "Seoul, Korea"
}
 

위 예제에서 Person 객체를 생성한 뒤, with 함수를 사용하여 Person 객체의 프로퍼티를 초기화합니다. 이때 with 함수는 람다식의 결과를 반환하므로, 이 경우에는 반환값이 없습니다.

 

 

let 함수

let 함수는 수신 객체를 인자로 받아 람다식을 실행하고, 람다식의 결과를 반환합니다. let 함수는 널 값을 체크하고, 널 값이 아닐 때만 람다식을 실행합니다.

 

let 함수의 구현체는 다음과 같습니다.

public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}

let 함수는 확장 함수로 정의되어 있으며, 제네릭 타입 T와 R을 인자로 받습니다. block 람다식은 T 타입을 인자로 받고, R 타입을 반환합니다.

 

이에 대한 예제는 다음과 같습니다. 아래 예제에서 name 변수는 널이 아닌 값인 "John"을 가지고 있습니다. 따라서 let 함수는 람다식을 실행하고, "John" 값을 출력합니다.

val name: String? = "John"
name?.let {
    println(it) // "John" 출력
}

 

아래 예제의 name 변수는 널 값을 가지고 있습니다. 따라서 let 함수는 람다식을 실행하지 않습니다.

val name: String? = null
name?.let {
    println(it) // 실행되지 않음
}
 

 

 

also 함수

also 함수는 apply 함수와 비슷하지만, also 함수는 객체의 상태를 변경하지 않고 객체 자체를 반환합니다. also 함수는 객체를 중간에 거쳐가는 값으로 사용할 때 유용합니다.

 

also 함수의 구현체는 다음과 같습니다.

public inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}

also 함수는 확장 함수로 정의되어 있으며, 제네릭 타입 T를 인자로 받습니다. block 람다식은 T 타입을 인자로 받고, 반환값이 없습니다. 또한 also 함수는 block 람다식을 수신 객체로 실행한 뒤, 해당 수신 객체를 반환합니다. 이를 통해, 수신 객체를 중간에 거쳐가는 값으로 사용할 수 있습니다.

 

이에 대한 예제는 다음과 같습니다. 아래 예제에서는 Person 객체를 생성한 뒤, also 함수를 사용하여 Person 객체의 프로퍼티를 초기화합니다. 이때 also 함수는 Person 객체를 반환하므로, personWithDetails 변수에는 초기화된 Person 객체가 할당됩니다.

val person = Person()
val personWithDetails = person.also {
    it.name = "John"
    it.age = 30
    it.address = "Seoul, Korea"
}

 

 

 

 

run 함수

run 함수는 수신 객체와 람다식을 인자로 받아 람다식을 실행하고, 람다식의 결과를 반환합니다. run 함수는 with 함수와 비슷하지만, run 함수는 람다식 내에서 수신 객체를 this 키워드로 참조할 수 있습니다.

 

run 함수의 구현체는 다음과 같습니다.

public inline fun <T, R> T.run(block: T.() -> R): R {
    return block()
}

run 함수는 확장 함수로 정의되어 있으며, 제네릭 타입 T와 R을 인자로 받습니다. block 람다식은 T 타입을 수신 객체로 받고, R 타입을 반환합니다. 그리고 run 함수는 block 람다식을 수신 객체로 실행한 뒤, 해당 결과를 반환합니다. 이를 통해, 람다식 내에서 수신 객체를 this 키워드로 참조할 수 있습니다.

 

이에 대한 예제는 다음과 같습니다. 다음 예제에서는 Person 객체를 생성하고, run 함수를 사용하여 Person 객체의 프로퍼티를 초기화합니다. 이때 run 함수는 람다식의 결과인 this를 반환하므로, person 변수에는 초기화된 Person 객체가 할당됩니다.

val person = Person().run {
    name = "John"
    age = 30
    address = "Seoul, Korea"
    this
}

 

 

 

 

Kotlin의 apply, with, run, let, also 에 대한 의의와 단점

이렇게 다양한 함수들이 제공되는 이유는, 객체를 다룰 때 다양한 용도와 상황에 맞게 사용할 수 있도록 하기 위함입니다. 각 함수들을 적절히 활용하면 코드의 가독성과 유지보수성을 높일 수 있습니다.

 

하지만 너무 다양하고 내부 기능이 비슷해서 너무 남발할 경우 오히려 유지 보수성이 나빠질 수 있다는 단점이 있습니다. 다른 팀원들과 함께 어떤걸 쓰고 안쓸지를 명확히 결정하고 쓰는 것이 좋을 것으로 보입니다.

반응형
댓글