코틀린에는 어떤 객체의 컨텍스트 내에서 하나의 코드 블록을 실행시키는 것이 목적인 함수들이 있습니다.
어떤 객체에서 이 함수들을 람다 표현식과 함께 호출하면, 하나의 일시적인 범위(Scope)를 형성합니다.
우리는 이 범위 안에서 해당 객체에 객체 이름없이 접근할 수 있습니다.
이런 함수를 범위(Scope)함수라고 합니다.
범위함수를 잘 사용하면, 깔끔하고 보기 쉬운 코드를 작성할 수 있습니다.
범위 함수에는 run,let,apply,also,with 다섯가지가 있습니다.
다섯가지 모두 제공된 코드 블록을 실행시키는데요, 범위 안에서 객체를 어떻게 참조하는지 그리고 무엇을 반환하는지가 다릅니다.
run은 객체를 this로 참조하고, 람다의 결과를 반환합니다.
fun main(){
val temp = System.`in`.bufferedReader().run{
val input = this.readLine()
"$input"
}
println(temp)
}
/*
>>>hello
hello
*/
run에 제공된 람다의 마지막 결과가 반환되어 temp에 저장되었습니다.
블록내부에서 객체에 접근할때 this를 써주었는데, 쓰지 않고 바로 메소드나 필드에 접근할 수 있습니다.
let은 객체를 it으로 참조하고, 람다의 결과를 반환합니다.
fun main(){
val list = listOf(1,2,3,4,5,6,7)
val processed = list.filter { it%2==0 }.let{
println(it)
it.map { it.times(2) }
}
println(processed)
}
/*
[2, 4, 6]
[4, 8, 12]
*/
그런데 it으로 참조하다보면 블록 내에서 다른 it과 겹치는 경우가 생깁니다.
위의 it.map{it.times(2)} 에서 앞의 it과 뒤의 it은 서로 다른 것을 가리킵니다.
앞의 it은 let의 컨텍스트 객체인 list.filter{it%2==0}를 가리키고,
뒤의 it은 map에서 사용되는 it으로 리스트의 원소인 Int입니다.
it이 겹치기 때문에,
let의 객체에 이름을 붙힘으로써, 가독성을 높일 수 있습니다.
fun main(){
val list = listOf(1,2,3,4,5,6,7)
val processed = list.filter { it%2==0 }.let{ filterdList ->
println(filterdList)
filterdList.map { it.times(2) }
}
println(processed)
}
/*
[2, 4, 6]
[4, 8, 12]
*/
let은 객체가 널이 아닌 경우에만 실행하고 싶은 블록이 있을 때에 잘 활용됩니다.
Safe Call이라고 불리는 " ?. " 을 활용합니다.
fun main(){
fun query(i:Int):String?{
return if(i==1) null else "Result"
}
var str = query(1)
str?.let { //str이 널이기 때문에 실행되지 않음
println("1")
println(it)
}
str = query(2)
str?.let { //str이 널이 아니기 때문에 실행됨
println("2")
println(it)
}
}
/*
2
Result
*/
also 는 it으로 참조하고, 컨텍스트 객체를 반환합니다.
fun main(){
val list = listOf(1, 2, 3, 4, 5, 6, 7)
val processed = list.filter { it % 2 == 0 }.also {
println(it)
}.filter { it > 4 }
println(processed)
}
/*
[2, 4, 6]
[6]
*/
with 는 this로 참조하고, 람다 결과를 반환합니다.
그러나 이 함수를 사용할때는 람다 결과를 제공하지 않는 것을 추천한다고 합니다.
fun main(){
val list = listOf(1, 2, 3, 4, 5, 6, 7)
with(list) {
println(size)
println(indices)
println(filter { it % 2 == 0 })
}
}
/*
7
0..6
[2, 4, 6]
*/
마찬가지로 this 없이도 바로 메소드나 필드 참조 가능합니다.
apply 는 it으로 참조하고, 컨텍스트 객체를 반환합니다.
apply는 초기화, 객체의 속성 설정 시에 잘 사용됩니다.
fun main(){
data class Car(
var color: String? = null,
var type: String? = null,
)
val redCar = Car().apply {
color = "RED"
type = "SUV"
}
println(redCar)
}
/*
Car(color=RED, type=SUV)
*/