일단 모두 지정된 객체의 유효범위를 람다식 내부로 제한하는 기능을 한다. 즉 어떠한 객체를 특정 변수에 할당해서 활용한다고 생각하면 되는데 그 영역을 블록으로 제한한다고 생각하면 된다. 각각의 의미만 나열하면 오히려 어떤걸 활용해야 할 지 감이 안 올 수 있기 때문에 연관지어서 정리하도록 하겠다.
제일먼저 with 는 가장 단순하다. 지정된 객체를 람다식 안에서 this 로 참조할 수 있고 객체를 수정할 수도 있으며 원하는 값을 리턴하거나 리턴하지 않을 수도 있다.
var map = mutableMapOf("A" to "aaa")
var map2 = with(map) {
this["A"] = "bbb"
this
}
var map3 = with(map) {
this["A"] = "ccc"
}
println(map)
println(map2)
println(map3)
---
{A=ccc}
{A=ccc}
kotlin.Unit
apply, also 는 일단 무조건 수신객체를 리턴한다. 리턴값을 별도로 지정할 필요도 없다. 람다식 안에서 수신객체가 변경되었되면 변경값 값을 리턴한다. 단, with 와 다르게 apply, also 는 확장함수로 제공된다. 즉, 아래와 같다.
var map = mutableMapOf("A" to "aaa")
var map2 = map.apply {
this["A"] = "bbb"
}
var map3 = map.also {
it["A"] = "ccc"
}
println(map)
println(map2)
println(map3)
다른점이 있다면 수신객체의 alias를 apply 는 this 로 사용하고 also 는 it 로 사용한다는 것인데 also는 아래와 같이 이 alias 를 이름을 원하는 이름으로 변경할 수 있지만 apply 는 변경할 수 없다.
var map3 = map.also { test ->
test["A"] = "ccc"
}
run 은 apply, also 와 달리 리턴값을 수신 객체 외에 다른 값으로 자유롭게 지정할 수 있다(unit 도 가능)
var map = mutableMapOf("A" to "aaa")
var map2 = map.run {
this["A"] = "bbb"
this
}
var str = map.run {
this["A"] = "bbb"
"test"
}
var unit = map.run {
this["A"] = "bbb"
}
println(map2)
println(str)
println(unit)
그럼 마지막으로 let 는 뭔가? 기본적으로 run 동일한 기능을 하는데 apply, also 를 구분하던 것과 마찬가지로 run은 수신객체 alias를 무조건 this 로 사용해야 하고 let는 it 외에 다른 것도 자유롭게 지정할 수 있다.
적절히 사용하겠지만 가장 많이 볼 수 있는 예는 let 이다. 이는 수신객체가 null 아닐경우 수행할 로직을 람다식으로 정의한다. 수신객체가 null 일 경우 아무것도 수행하지 않는다. 일반적으로 아래와 같이 사용한다. apply, also 와 달리 수신객체가 아니라 원하는 값을 리턴할 수 있다.(unit 도 가능)
val str: String? = null
str?.let {
println(it.reversed())
}
그런데 만약 이와 반대로 수신객체가 null 일 경우 처리할 로직은 어떻게 정의해야 할까? if(str == null) 과 같은 조건문을 사용해야 할까? 그렇지 않다. 아래와 같이 엘비스 연산자와 run 을 활용하면 더 깔끔하다.
val str: String? = null
str?:run{
println("str is null")
}
아래와 같이 let 과 엘비스연산자+run 을 함께 조합하면 null 이 아닌 경우와 null 경우를 한번에 처리할 수 있다.
val str: String? = null
str?.let {
println(it.reversed())
}?:run{
println("str is null")
}
그런데 사실 엘비스연산자+run 조합은 let 없이 단독으로 쓰이면 오버스팩에 가깝다. 수신객체가 null 이기 때문에 람다식 안에서 수신객체가 활용될 일이 없는데 굳이 수신객체를 전달받는 람다식을 쓸 이유가 없다. 이 경우 if(str == null) 로 끝내는 것이 오히려 깔끔하다.
'Backend > Kotlin' 카테고리의 다른 글
[Kotlin] 함수형 인터페이스 (0) | 2021.12.31 |
---|---|
[Kotlin] 확장함수 언제 어떻게 사용해야 할까? (0) | 2021.12.24 |
[Kotlin] interface default method (0) | 2021.12.22 |
[Kotlin] 제네릭(내가 필요한 것만 정리..) (0) | 2021.12.06 |
[Kotlin] inline (0) | 2021.12.06 |