티스토리 뷰
[코틀린/kotlin] 영역 함수 (scope function) also, let, run, with, apply
hrniin 2023. 11. 1. 20:43
영역 함수는 내가 코틀린에서 가장 좋아하는 기능이고, 그만큼 많이 사용해 왔다.
불필요한 일회성 변수를 선언하지 않아도 되고, 코드의 가독성이 증가하는 점이 좋다!
하지만 정작 영역 함수 6개가 어떤 차이를 가지는지, 어떤 경우에 사용하면 적절한지
확실히 알지 못하고 있는 것 같아 제대로 알아보려고 한다..
영역 함수는 범위 함수, 범위 지정 함수라고도 불린다.
영역 함수가 필요한 경우 (1)
val user = User()
user.name = "abc"
user.age = 20
print(user)
영역 함수를 사용하지 않은 경우의 간단한 예제를 작성해봤다.
user 객체의 프로퍼티를 변경하고, user를 출력하기 위해 객체명인 user를 계속해서 사용한다.
val user = User().apply {
name = "abc"
age = 20
print(this)
}
영역 함수를 사용한 경우 코드가 깔끔해지는 것을 볼 수 있다.
객체를 생성한 후 그 객체에 대한 설정값을 변경하고 싶은 경우 위처럼 한줄로 표현이 가능하다.
영역 함수가 필요한 경우 (1)
val user = User()
user.name = "abc"
user.age = 20
val address = user.getAddress()
또한 이 코드에서 user 객체는 앞으로 사용되지 않는다고 가정할 때,
영역 함수를 사용하지 않는 경우 일회성으로 사용되는 객체임에도 불구하고 메모리에 할당되어있다.
val address = User().run {
name = "abc"
age = 20
getAddress()
}
영역 함수를 사용하면 불필요한 메모리 할당을 줄일 수 있고 가독성도 좋아진다!
그럼 영역 함수 하나씩 어떤 특성을 가지는지 알아보자.
let
inline fun <T, R> T.let(block: (T) -> R): R
영역 함수를 호출하는 receiver의 확장 함수 형태로 선언되어 있다.
람다식의 인자가 receiver 하나인 경우(T), it으로 receiver에 접근할 수 있다.
val address = User().let {
it.name = "abc"
it.age = 20
it.getAddress()
}
람다식의 결과, 즉 람다식 가장 마지막 문장의 return 값을 반환한다.
람다식 안에서는 it으로 receiver에 접근할 수 있고, it을 생략할 수는 없다.
val address = user?.let {
it.name = "abc"
it.age = 20
it.getAddress()
} ?: return
?.(safe-call)과 함께 사용해서 non-null인 경우에만 let 안의 block을 수행하도록 작성할 수 있다.
run
fun <T, R> T.run(block: T.() -> R): R
let과 비슷하지만 람다식의 타입이 다르다.
람다식의 인자 T.()를 람다 리시버라고 하고,
람다 리시버인 람다식은 this를 따로 작성하지 않아도 receiver의 속성에 접근할 수 있다.
val address = User().run {
name = "abc"
age = 20
print(this)
getAddress()
}
람다식의 결과를 반환한다.
this를 사용해서 receiver에 접근할 수 있고, 생략 가능하다.
let과 동일하게 ?.(safe-call)과 함께 사용해서 안전하게 함수를 호출할 수 있다.
run
fun <R> run(block: () -> R): R
run은 두가지 형태로 선언되어 있는데, 일반적인 영역 함수와 다르게 receiver가 없고 확장 함수가 아니다.
람다식에 인자도 존재하지 않는다.
val user = run {
val name = "abc"
val age = 12
User(name, age)
}
일반적으로 어떤 객체를 생성하기 위한 문장들을 하나의 블럭 안에 묶어서 가독성을 높일 때 사용한다.
let, 확장 함수 run과 동일하게 람다식의 결과를 반환한다.
with
fun <T, R> with(receiver: T, block: T.() -> R): R
with는 인자가 두개인 일반 함수다.
첫 번째 인자는 receiver, 두 번째 인자는 receiver의 람다 리시버 타입인 람다식이다.
val person = User("abc", 20)
...
with(person) {
name = "def"
age = 25
print(this)
}
반환 값은 람다식의 결과가 된다.
람다 리시버 타입이므로 람다식 안에서는 this를 사용하여 receiver에 접근하고, 생략이 가능하다.
일반적으로 위 코드처럼 이미 생성된 객체의 속성을 변경하고자 할 때 사용한다.
apply
fun <T> T.apply(block: T.() -> Unit): T
receiver의 확장 함수이며, 람다 리시버 타입이다.
지금까지 설명했던 영역 함수와는 다르게, 람다식의 반환값이 Unit이고, apply 함수의 반환값은 T이다.
val user = User().apply {
name = "abc"
age = 20
print(this)
}
일반적으로 객체를 생성하면서 객체의 속성값을 변경하고, 그 객체를 변수로 저장하고 싶을 때 사용한다.
람다식 안에서는 this를 사용하며 생략이 가능하다.
also
fun <T> T.also(block: (T) -> Unit): T
apply와 유사하지만 람다식의 인자가 (T)인 점이 다르다.
람다식 안에서 it을 통해 receiver에 접근하며 it을 생략할 수 없다.
val user = User().also {
it.name = "abc"
it.age = 20
print(it)
}
이 코드처럼 receiver의 속성을 변경해야 할 경우 it을 일일이 작성해 주어야 한다.
그래서 이 경우 apply를 주로 사용하며, also는 유효성 검사를 하거나 디버깅을 할 때 사용한다.
val numbers = arrayListOf("one", "two", "three")
numbers
.also { println("add 하기 전에 print: $it") }
.add("four")
정리
함수명 | receiver | 반환값 | 확장 함수 |
let | it | 람다식의 결과 | O |
run | this | 람다식의 결과 | O |
run | X | 람다식의 결과 | X |
with | this | 람다식의 결과 | X |
apply | this | receiver | O |
also | it | receiver | O |
참고
https://blog.yena.io/studynote/2020/04/15/Kotlin-Scope-Functions.html
'app > kotlin' 카테고리의 다른 글
[코틀린/kotlin] 클래스 내에서 프로퍼티에 접근하면 커스텀 접근자가 호출될까? (1) | 2024.02.24 |
---|---|
[코틀린/kotlin] DTO와 VO의 차이 (1) | 2023.11.01 |
[코틀린/kotlin] 콘솔창에 출력된 값 테스트하기 (AssertJ, Junit) (0) | 2023.10.31 |
[kotlin/코틀린] JUnit, AssertJ 어노테이션과 함수 정리하기 (0) | 2023.10.22 |
코틀린으로 코딩테스트 준비하기 (0) | 2023.10.18 |