수신 객체 지정 람다 (람다 리시버, Lambda Receiver)
코드를 작성하다 보면 사용하는 객체를 반복해서 명시하는 경우가 많은데, 수신 객체 지정 람다는 이런 수신 객체(Lambda
Object
)를 명시하지 않고 람다의 본문 안에서 다른 객체의 메소드를 호출할 수 있게 하는 것을 말한다.
이것이 가능한 이유는 블록(block
) 람다식에서 수신 객체를 람다의 수신 객체 또는 람다의 입력 파라미터로 사용하였기 때문이다.
범위 함수 종류
// apply
inline fun <T> T.apply(block: T.() -> Unit): T
// run
inline fun <T, R> T.run(block: T.() -> R): R
inline fun <R> run(block: () -> R): R
// with
inline fun <T, R> with(receiver: T, block: T.() -> R): R
// let
inline fun <T, R> T.let(block: (T) -> R): R
// also
inline fun <T> T.also(block: (T) -> Unit): T
// 제네릭 약어
// E - Element
// K - Key
// N - Number
// T - Type
// V - Value
// R - Return Type
apply
수신 객체의 내부 프로퍼티를 설정한 후 수신 객체 자신을 반환하는 함수이다.
- 람다식의 수신 객체가
apply
의 수신 객체여서 수신 객체에 대한 명시(this
)를 생략하는 것이 가능하다. - 객체 생성 시에 다양한 프로퍼티를 설정해야 하는 경우에 사용한다.
inline fun <T> T.apply(block: T.() -> Unit): T
// T.apply 의 T 는 apply 의 수신 객체
// block: T.() 의 T 는 람다의 수신 객체
// inline fun <T> T.apply(block: T.() -> Unit): T
fun main() {
val user = User().apply {
name = "BBB"
age = 20
}
}
data class User(
var name: String = "AAA",
var age: Int = 0
)
run
apply
와 같은 동작을 하지만 블록의 마지막 라인을 반환하는 함수이다.
- 블록의 마지막 라인이 없다면
Unit
을 반환한다. - 수신 객체에 대한 특정한 동작 수행 후 결과값을 반환 받아야 할 경우 사용한다.
inline fun <T, R> T.run(block: T.() -> R): R
// T.run 의 T 는 run 의 수신 객체
// block: T.() 의 T 는 람다의 수신 객체
// R 은 반환 객체
inline fun <R> run(block: () -> R): R
// R 은 run 의 반환 객체
// inline fun <T, R> T.run(block: T.() -> R): R
fun main() {
var str: String = "TEST code"
str = str?.run {
println(this.toUpperCase())
println(toLowerCase())
"Complete"
}
println(str)
}
// TEST CODE
// test code
// Complete
// inline fun <R> run(block: () -> R): R
fun main() {
val result = run {
val items = 10
val price = 1000
val discountPercent = 10.0
((items * price) * ((100 - discountPercent) * 0.01)).toInt()
}
println ("가격 : ${result}원 ")
}
// 가격 : 9000원
with
run
과 같은 동작을 하는 함수로 run
은 수신 객체의 확장 함수이지만 with
는 수신 객체를 파라미터로 받는 함수이다.
Nullable
을 허용하지 않는다.
inline fun <T, R> with(receiver: T, block: T.() -> R): R
// receiver: T 의 T 는 with 의 수신 객체
// block: T.() 의 T 는 람다의 수신 객체
// R 은 반환 객체
// inline fun <T, R> with(receiver: T, block: T.() -> R): R
fun main() = with(str) {
println(toUpperCase())
println(toLowerCase())
}
private val str: String = "TEST code"
// TEST CODE
// test code
let
run
과 같은 동작을 하는 함수로 run
은 수신 객체 자신을 그대로 전달하지만 let
은 수신 객체를 복사하여 전달하기 때문에 수신 객체에 접근을 할 때 참조 연산자 it
을 사용해야 한다.
null
체크 후 코드를 실행하거나Nullable
한 수신 객체를 다른 타입의 값으로 반환해야 하는 경우 사용한다.
inline fun <T, R> T.let(block: (T) -> R): R
// T.let 의 T 는 let 의 수신 객체
// block: (T) 의 T 는 람다의 파라미터
// R 은 반환 객체
// inline fun <T, R> T.let(block: (T) -> R): R
fun main() {
val user = User().age?.let {
var age = 10 + it.age
age
}
println("$user")
}
data class User(
var name: String = "AAA",
var age: Int? = 0
)
// User(name=AAA, age=10)
also
apply
와 동일하게 수신 객체 자신을 반환하는 함수로 let
과 동일하게 수신 객체를 복사하여 전달하기 때문에 수신 객체에 접근을 할 때 참조 연산자 it
을 사용해야 한다.
- 수신 객체의 내부 프로퍼티 설정뿐만 아니라 유효성 검사와 같은 추가적인 작업 후 수신 객체 자신을 반환해야 하는 경우 사용한다.
inline fun <T> T.also(block: (T) -> Unit): T
// T.also 의 T 는 also 의 수신 객체
// block: (T) 의 T 는 람다의 파라미터
// inline fun <T> T.also(block: (T) -> Unit): T
fun main(user: User) {
val user = user.also {
requireNotNull(user.age)
}
}
data class User(
var name: String = "AAA",
var age: Int = 0
)