(Kotlin) 수신 객체 지정 람다 - with / apply / let / run / takeif / also
수신 객체 지정 람다
with : prefix없이 접근하고 싶을 때
with
는 원래 파라미터가 2개인 함수다. 그러나 두 번째 인자인 람다를 밖으로 빼서 원래 언어가 지원하는 구문인 것 처럼 사용할 수 있다. 보기 깔끔해진다. with
의 람다 내에서는 전달된 객체에 prefix없이 접근할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
fun alphabet(): String {
val stringBuilder = StringBuilder()
return with(stringBuilder) {
for (letter in 'A'..'Z') {
this.append(letter)
append("!") // this 생략 가능
}
this@OuterClass.somFunc() // 바깥쪽 클래스 멤버 접근
this.toString() // return
}
}
또는 이렇게 리팩토링 할 수 있다.
1
2
3
4
5
6
fun alphabet() = with(StringBuilder()) {
for (letter in 'A'..'Z') {
append(letter)
}
toString()
}
내부에서 널체크를 수행해주어야 한다는 단점이 있음. 이런 경우 let
을 고려한다.
apply : 객체를 만들면서 인스턴스를 초기화하고 싶을 때
apply
함수는 with
와 거의 비슷한데, 확장 함수로 정의되어 있으며 자신에게 전달된 객체를 리턴한다는 차이점이 있다. 위 함수를 apply
를 이용해 리팩토링하면 다음과 같다.
1
2
3
4
5
fun alphabet() = StringBuilder().apply {
for (letter in 'A'..'Z') {
append(letter)
}
}.toString()
apply
함수는 객체의 인스턴스를 만들면서 즉시 프로퍼티 중 일부를 초기화해야 할 때 유용하다. apply
내에서 호출하는 메소드나 프로퍼티는 수신 객체의 메소드나 프로퍼티를 의미한다.
1
2
3
4
5
6
fun createViewWithCustomAttributes(context: Context) =
TextView(context).apply {
text = "Sample"
textSize = 20.0
setPadding(10, 0, 0, 0)
}
위 코드를 풀어 쓰면 이렇게 된다.
1
2
3
4
5
6
7
fun createViewWithCustomAttributes(context: Context) {
val t = TextView(context)
t.text = "Sample"
t.textSize = 20.0
t.setPadding(10, 0, 0, 0)
return t
}
apply
vsBuilder
- apply가 더 낫다.
- cmd + B로 따라갔을 때 해당 필드가 바로 보이고,
- apply 구문 내에서 복잡한 초기화도 가능하다.
let : 널이 될 수 있는 타입 인자로 넘기기
bar?.method()
는 메소드 호출에 사용할 수 있지만, 함수에 f(bar)
를 넘길 때는 사용할 수 없다. 이런 경우 let
을 사용할 수 있다.
1
2
3
4
getTheBestPerson()?.let { sendEmailTo(it.email) } // 아래 구문과 동일
val person: Person? = getTheBestPerson()
if (person != null) sendEmailTo(person.email)
변수를 할당하지 않아도 된다는 장점이 있지만 let
을 중첩하면 가독성이 떨어진다는 단점이 있다.
run 활용
1
2
3
adapter ?: run { . . . }
if (adapter == null) { . . . }
takeif 활용
takeif
는 조건이 참일 때 this
를 리턴하고, 참이 아니면 null
을 리턴한다.
1
2
3
4
5
6
// Original code
if (mBluetoothAdapter == null) { A }
if (!mBluetoothAdapter.isEnabled()) { B }
// Improved code
mBluetoothAdapter?.takeIf{ it.isEnabled() }?.run { B } ?: A
run let with apply also의 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
14
inline fun <T, R> T.run(block: T.() -> R): R = block()
inline fun <T, R> T.let(block: (T) -> R): R = block(this)
inline fun <T, R> T.with(receiver: T, block: T.() -> R): R = 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
}
// use { }는 open하는 자원에 연결 시 close()를 자동으로 호출해준다.
// try-with-resources 에 대응한다고 보면 된다.
runCatching
try-catch-finally 대신 runCatching 도 사용 가능함. catch문 결과를 반환해주기 때문에 indent depth를 줄일 수 있음.
1
2
3
4
5
6
7
8
runCatching {
get()
}.fold(
onSuccess = { onSuccess(it) },
onFailure = { onFailure(it) }
).also {
finally()
}
언제 사용하면 좋은지? (let vs run)
https://kotlinlang.org/docs/scope-functions.html#function-selection
Here is a short guide for choosing scope functions depending on the intended purpose:
- Executing a lambda on non-nullable objects:
let
- Introducing an expression as a variable in local scope:
let
- Object configuration:
apply
- Object configuration and computing the result:
run
- Running statements where an expression is required: non-extension
run
- Additional effects:
also
- Grouping function calls on an object:
with