코틀린은 기본적으로 표준 스코프 함수 라는 것을 제공한다.
스코프 함수는 특정 객체의 컨텍스트 내에서 특정 동작 (프로퍼티 초기화, 활용 등) 을 실행하기 위한 목적만을 가진 함수다. 람다 식을 사용하여 객체에 이러한 함수들을 호출하면 임시적으로 스코프(범위)가 설정된다. 이 범위에서는 해당 객체의 이름 없이 접근할 수 있다.
총 5가지의 함수가 존재한다.
let run with apply also
이 5개의 함수는 기본적으로 같은 일을 한다.
객체에 붙어 있는 코드 블록을 실행
그렇다면 이 5개의 함수의 차이점은 무엇일까?
해당 객체를 어떻게 코드 블록 내에서 사용 가능하게 만드는가?
전체 표현식의 결과가 무엇인가?
5가지 함수의 정의는 아래와 같다.
inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
data class Member(val name: String, var age: Int)
val member = Member("Wangi", 26)
val nextYearHisAge = member.run {
++age // this.age
}
println(nextYearHisAge) // 27
이미 만들어진 인스턴스의 값 혹은 그를 이용한 특정 계산 결과를 필요로 하는 경우, run을 활용해 이를 반환 받아볼 수 있다.
위의 run과 다른 형태의 run도 존재한다.
inline fun <R> run(block:() -> R): R{
return block()
}
이 run은 확장 함수도 아니고, 블록에 입력값도 없다. 따라서 객체를 전달받아서 속성을 변경하는 형식에 사용되지 않는다.
이 함수는 단지 어떤 객체를 생성하여 명령문을 블록 안에 적음으로써 가독성을 높이는 역할을 한다.
val member = run {
val name = "Wangi"
val age = 26
Member(name, age)
}
val member = Member("Wangi", 26)
with(member) {
println("This member name is $name") // this.name
println("This member age is $age") // this.age
}
val member = Member("Wangi").apply{
age = 26 // this.age
}
println(member) // Member(name=Wangi, age=26)
인스턴스를 새로 생성하고 특정 변수에 할당하기 전에 초기화 작업을 해줄 수 있는 스코프를 만들어 준다.
따라서 apply 함수 내의 모든 명령이 수행되고 나면 명령들이 적용되어 새로 생성된 인스턴스를 반환한다는 특징을 갖고 있다.
class Membership(member: Member) {
val member = member.also {
requireNotNull(it.age)
println(it.name)
}
}
also를 사용하지 않는 동일한 코드는 아래와 같다.
class Membership(val member: Member) {
init {
requireNotNull(member.age)
println(member.name)
}
}
getMember()?.let {
// null이 아닐때만 실행
println(it) // it: member
}
val length = str?.let {
println("this str is not null")
it.length
} ?: 0
// str이 "Wangi"일 경우 length = 5
// str이 null일 경우 length = 0
fun main() {
val price = 99999999
var a = Book("해로의 모험", 1000).apply {
name = "[폭탄세일중]" + name
discount()
}
a.run {
println("상품명 : ${name}, 가격 : ${price}")
}
}
상품명 : [폭탄세일중]해로의 모험, 가격 : 99999999
main() 스코프 내에 인스턴스 프로퍼티와 이름이 같은 변수가 있어서 이를 출력해버렸다.
run 에서 상위 스코프인 main() 스코프의 동명의 변수를 참조한 것이다.
also와 let은 이와 같은 혼란을 방지하기 위해서 it 이라는 키워드를 제공해준다.
class Book(var name: String, var price: Int) {
fun discount() {
price -= 200
}
}
fun main() {
val price = 99999999
var a = Book("해로의 모험", 1000).apply {
name = "[폭탄세일중]" + name
discount()
}
a.let {
println("상품명 : ${it.name}, 가격 : ${it.price}")
}
}
상품명 : [폭탄세일중]해로의 모험, 가격 : 800
main() 스코프의 price가 아닌 a 인스턴스의 프로퍼티를 출력하게 되었다.
it 키워드에 참조 연산자를 통해 프로퍼티 및 함수를 접근하면 된다.
위의 스코프 함수들은 새로운 기술이 아니라, 코드들을 더욱 간결하고 가독성 좋게 하자는 의도로 사용한다.
https://mycool0905.github.io/kotlin/2020/12/15/kotlin-scope-function.html
https://haero.tistory.com/21