본문 바로가기
2023년 이전/kotlin

Kotlin - let, apply, run, with, also

by JeongUPark 2019. 11. 20.
반응형

kotlin을 사용하면서 유용하게 쓰고 많이 쓰는 함수 let, apply, run, with, also 가 있습니다. 이들에 대하여 정리하려 합니다.

 

let

let의 원형은 아래와 같습니다.

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

호출하는 객체가 블록의 인자로 들어가고 그 수행 결과를 반환합니다

원문에서는 다음과 같이 설명합니다

The context object is available as an argument (it). The return value is the lambda result.

 

예제로 보면

var person = Person("jeongu", "park")
var  result = person?.let {
    it.FristName = "aaa"
    it.LastName = "bbb"
    it.LastName
}

class Person(fristname : String, lastName : String){

    var FristName = fristname
    var LastName = lastName

}

이렇게 수행할 경우 위의 it은 객체인 person이 되고 마지막 수행결과인 it.LastName이 반환되어 result가 됩니다.

(Person class는 계속 사용하도록 하겠습니다.)

 

일반적으로 let은 함수를 호출한 객체를 인자로 받으므로, 이를 사용하여 다른 함수를 실행하거나 연산을 수행해야 하는 경우 사용할 수 있습니다. 그리고 안전한 호출(Safe Calls - ?.)과 함께 사용하면 if (null != obj) ... 를 대체할 수 있습니다.

 

 

apply

apply의 원형은 다음과 같습니다.

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

함수를 호출한 객체를 블락의 리시버로 전달하고 그 객체를 반환합니다. 

원문에서는 다음과 같이 설명합니다

 

The context object is available as a receiver (this). The return value is the object itself.

 

예를 들어 보겠습니다.

var result = person.apply {
    FristName = "eee"
    LastName = "fff"
}

이렇게 사용하면 위에서 FristName 이 jeongu,  LastName이 park 였던 내용이 eee/fff로 변경이 되고 그 person이 반환되어 result에 적용 됩니다.

apply는 특정 객체를 생성하면서 함께 호출해야 하는 초기화 코드가 있는 경우 사용할 수 있습니다. 즉, 람다 내부에서 함수를 사용하지 않고 생성된 객체를 초기화 할 때 많이 사용합니다.

 

with

with의 원형은 다음과 같습니다.

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

인자로 받는 객체를 이어지는 블록의 리시버로 전달하며, 블록의 결과값을 반환합니다. 

원문에서는 다음과 같이 설명하고 있습니다.

A non-extension function: the context object is passed as an argument, but inside the lambda, it's available as a receiver (this). The return value is the lambda result.

간단하게 원문을 보면

1. 확장함수가 아니다.

2. 객체는 인수로 전달되지만, 람다 내부에서는 리시버로 사용된다.

3. 수행 결과를 반환한다.

입니다.

 

예제로 보면

var result = with(person){
    this.LastName = "ccc"
    this.FristName= "ddd"
    this
}

person 객체가 인수로 들어가고 with 람다 내에서 this가 있는데 이 값이  위에서 인자로 들어간 객체가 리시버가 된다 하였기 때문에 이는 with(person)에서 person임을 알 수 있습니다. 그래서 firstname과 lastname이 변경하고, 마지막 수행된 this가 반환되어 result에 적용 됩니다. result 역시 person 입니다.

with는 들어가는 인수값이 Non-null 이 어야 합니다.

마지막으로 with는 람다 결과를 제공하지 않고 컨텍스트 객체에서 함수를 호출하려면 'with'를 권장합니다. 즉 with는 다음과 같은 의미를 가집니다.  with this object, do the following.

also

also의 원형은 다음과 같습니다.

public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

함수를 호출한 객체가 블럭의 인수가 되고 그 객체를 다시 반환합니다.

원문은 다음과 같습니다

The context object is available as an argument (it). The return value is the object itself.

예제를 보면

var result = person.also {
    println(it.FristName)
    println(it.LastName)
}

person이 also를 호출 하고 그 person의 내용을 확인하고 있습니다.

also는 일반적으로 자신을 호출한 객체를 사용하지 않거나, 값을 변경하지 않을 경우에 쓰이는 함수입니다. 그래서 also 는 일반적으로 디버그 정보 로깅 또는 인쇄와 같이 오브젝트를 변경하지 않는 추가 조치에도 사용합니다.

 

run

run함수의 원형은 다음과 같습니다.

public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

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

run 함수는 객체없이 호출하는 형태나, 객체에서 호출하는 2가지 형태가 있습니다.

객체 없이 호출할 경우 그 블락내에서 처리한 결과값(마지막 수행)을 반환 합니다.

객체에서 호출할 경우에는 객체를 블락 리시버로 가지며 그 결과값(마지막 수행)을 반환합니다.

 

원문은 다음과 같습니다.

The context object is available as a receiver (this). The return value is the lambda result.

 

객체에서 run을 호출하는 경우 객체를 리시버로 전달받으므로, 특정 객체의 메서드나 필드를 연속적으로 호출하거나 값을 할당할 때 사용합니다. apply()와 적용 예가 유사하지만, apply()는 새로운 객체를 생성함과 동시에 연속된 작업이 필요할 때 사용하고 run()은 이미 생성된 객체에 연속된 작업이 필요할 때 사용한다는 점이 조금 다릅니다. 그리고 반환값도 다릅니다.(apply를 객체를, run은 마지막 수행 결과를) 

 

마지막으로 run은 전한 호출(Safe Calls - ?.)과 함께 사용할 수 있으면, 이경우 객체가 null 이 아닐 경우에만 블럭이 수행 됩니다.  또한, run은 with와 기능은 동일합니다. (객체의 위치가 다르고, with는 null인 객체는 사용 못함). 

즉, run은 let과 with를 사용하여 동일한 기능으로 사용할 수 있습니다.

var result = person?.let{
	with(it){
        "${it.FristName} is not orignal"
    }
}

 

 

참고

https://kotlinlang.org/docs/reference/scope-functions.html#let

https://www.androidhuman.com/lecture/kotlin/2016/07/06/kotlin_let_apply_run_with/

https://jepark-diary.tistory.com/69

 

반응형