.

코틀린에서 자주 사용되는 inline 함수 apply, with, let, also, run 의 차이점은 아래와 같습니다.

input – receiver/parameter 는 5종의 inline 함수가 호출될 때 참조할 객체와 어떤 관계를 가지고 호출되는지를 나타냅니다.

  • receiver 타입인 apply, run, also, let 은 해당 객체의 확장 함수 형태로 호출됩니다.
  • parameter 타입인 with 는 참조할 객체를 파라미터로 넘깁니다. 그리고 파라미터로 넘긴 객체의 확장 함수 형태로 코드블록이 실행됩니다.

apply 함수와 with 함수의 정의와 용례를 보면 어떤 내용인지 알 수 있습니다.

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

class Person {
    var name: String? = null
    var age: Int? = null
}

val person = Person().apply {
    name = "Peter"
    age = 18
}

apply 함수는 Person 객체의 확장 함수처럼 실행되므로 name, age 값을 직접 변경합니다. 코드블록 안에서 this 를 이용해서 person 객체를 참조할 수 있습니다.

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

val person: Person = Person()
with(person) {
    name = "Peter"
    age = 18
}

반면 with 에서는 파라미터로 넘긴 person 객체의 확장 함수처럼 실행됩니다. 이 경우도 코드블록 안에서 this 를 이용해서 person 객체를 참조할 수 있습니다.

.

binding in lambda – receiver/parameter 는 inline 함수가 정의한 코드블록 안에서 객체를 어떻게 참조할 수 있는지를 나타냅니다.

  • receiver 타입인 apply, run 은 해당 객체의 확장 함수처럼 실행됩니다. 그리고 this 를 이용해 객체를 참고할 수 있습니다.
  • parameter 타입인 also, let 은 해당 객체를 parameter 로 받습니다.

run, let 함수의 정의와 용례를 보겠습니다.

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

fun start() = person.run {
    print(name)
    print(age)
}

run 의 정의를 보면 T.run(block: T.() -> R): R 코드블록이 객체의 확장 함수처럼 실행됨을 알 수 있습니다. 따라서 객체를 this 로 참조할 수 있습니다. 대신 참조의 범위가 객체 내부로 한정됩니다.

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

fun print() {
    val address: String = "Korea"
    
    person?.let {
        print(it.name)
        print(address)
    }
}

let 의 정의를 보면 T.let(block: (T) -> R): R 코드블록이 객체를 파라미터로 받음을 알 수 있습니다. 따라서 코드블록 내에서 it 으로 참조할 수 있습니다. 대신 참조의 범위가 let 이 실행되는 코드를 포함한 블록이 됩니다.(예제에서는 print() 함수)

.

output – same object, result of lambda 는 인라인 함수가 무엇을 리턴하는지 알려줍니다.

  • same object 는 참조한 객체를 리턴합니다.
  • result of lambda 는 코드블록에 명시한 실행 결과를 리턴합니다.

also 함수의 정의와 예제를 보겠습니다. also 함수는 input – receiver 타입이며 binding in lambda – parameter 타입이고, output – same object입니다.

따라서 참조할 객체의 확장 함수처럼 사용하며, 참조할 객체를 파라미터로 받습니다. 그리고 참조한 객체를 리턴합니다.

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

class Person {
    var name: String? = null
    var age: Int? = null
    fun printName() { print(name) }
}

fun sample() {
    person.also {
        print(it.age)
    }.printName()
}

let 함수의 경우 다른 특성은 also 와 같지만 output – result of lambda 이므로 코드블록 실행 결과를 리턴합니다.

fun sample() {
    var myName: String? = person.let {
        it.name
    }
}

.

.

정리

  • 참조할 객체에 binding 하는 속성을 판단
    • with 의 경우만 parameter 형태로 객체를 전달, 나머지는 객체의 확장 함수처럼 사용
  • 객체가 코드 블록 내부에서 어떻게 참조되는가
    • apply, run, with 의 경우 receiver 형태로 실행되므로 this 로 참조
    • also, let 의 경우는 객체를 파라미터로 받으므로 it 으로 참조
    • receiver/parameter 의 차이에 따라 코드 블록이 참조할 수 있는 범위 제한이 바뀜
  • 코드 블록 실행 후 무엇을 리턴하는가
    • apply, also 는 참조한 객체
    • run, let 은 코드 블록의 실행 결과

.

참고

.

.