Object context 내에서 코드 블럭을 실행하는 코틀린의 5가지 함수를 말합니다.
블록 내에서는 객체의 이름을 명시하지 않아도 해당 객체에 접근할 수 있어서 코드를 보다 간결하게 작성할 수 있도록 해주지만, 중첩되면 오히려 가독성을 해치게 되는 경우가 발생하니 너무 남발하지 않는 것이 좋습니다.
종류로는 with, run, also, apply, let가 있으며, 처음 접할 때에는 모두 비슷한 기능을 한다고 느낄 수 있어서 각각의 차이점 알고 사용해야 합니다.
Scope 함수의 차이점은 크게 두 가지 입니다.
위 두 가지 차이점을 중점으로 5가지의 Scope 함수를 알아보도록 하겠습니다.
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
run은 확장함수이기 때문에, 객체를 this로 접근할 수 있으며, 람다식의 결과 값을 리턴 합니다.
따라서, 접근하는 객체의 메소드나 프로퍼티를 이용한 결과 값이 필요한 경우에 사용합니다.
그 외에도 객체의 메소드에 여러 번 접근할 때도 사용합니다.
사용 예시
val book1 = Book("동화",1000)
val salePrice = book1.run { price * 0.8 } // 800
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
with는 확장함수가 아니지만, 파라미터로 받은 객체를 블록의 파라미터로 받기 때문에, this로 접근할 수 있으며, 람다식의 결과 값을 리턴합니다.
수신 객체가 Non-nullable일 때만 사용 가능하며, 결과 값이 필요 없을 때 주로 사용합니다.
run과는 확장함수의 여부 외에는 큰 차이점이 없습니다.
사용예시
val book1 = Book("동화",1000)
with(book1) {
println( "제목 : $title, 가격 : $price" )
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
apply는 확장함수이기 때문에, 객체를 this로 접근할 수 있으며, 객체 자신을 리턴합니다.
주로 객체의 프로퍼티를 변경 또는 지정해주면서 초기화 할 때 사용합니다.
사용예시
val morning = Car().apply {
name = "morning"
price = 100
}
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
let은 확장함수지만, 람다식에서 파라미터로 수신 객체를 받기 때문에, it을 통해 객체에 접근이 가능합니다.
리턴값은 람다식의 결과값 입니다.
주로 객체의 null check를 할 때 사용합니다.
사용예시
fun test(str: String?) {
str?.let {
println("result :: $it")
} ?: println("result is null")
}
test에서 주어진 파라미터 str이 null이라면 result is null을 출력하고, null이 아니라면 result :: $it을 출력하게 됩니다.
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
also는 확장함수지만, 람다식에서 파라미터로 수신 객체를 받기 때문에 객체에 it을 통해 접근하며, 객체 자신을 리턴합니다.
객체의 초기화에 주로 사용되는 apply와 다르게, 객체 내 프로퍼티를 변경하지 않고 사용할 때 also를 사용합니다.
사용예시
val soul = Car()
soul.also { println("car name: ${it.name}") }
코틀린에서 제공하는 Scope 함수는 객체 접근 방식과 리턴값으로 구분할 수 있으며 다음과 같습니다.
수신 객체 자신 | 람다식 결과 | |
---|---|---|
this | apply | run, with |
it | also | let |