let
은 함수를 호출하는 객체 T
를 block
의 인자로 넘기고 block
의 결괏값 R
을 반환합니다.
public inline fun <T, R> T.let(block: (T) -> R): R { ... return block(this) }
let
의 매개변수로는 람다식 형태인 block
이 있고, T
를 매개변수로 받아 R
을 반환합니다. 함수 본문의 this
는 객체 T
를 가리킵니다.
val padding = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
16F,
resources.displayMetrics
).toInt()
tvHelloWorld.setPadding(padding, 0, padding, 0)
위의 코드에서처럼 padding
이 한 번만 사용되면 변수 할당을 하느라 자원 낭비가 있을 수 있습니다.
이때 아래와 같이 let()
함수를 사용할 수 있습니다.
TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
16F,
resources.displayMetrics
).toInt().let { padding->
tvHelloWorld.setPadding(padding, 0, padding, 0)
}
📌 정리
non-null 객체에 대해 람다식을 실행하거나 local scope 에서 변수로 표현식을 소개하기 위해 사용합니다.
also
는 함수를 호출하는 객체 T
를 block
에 전달하고 객체 T
자체를 반환합니다.
public inline fun <T> T.also(block: (T) -> Unit): T = { block(this); return this }
also
는 블록 안의 코드 수행 결과와 상관없이 this
(객체 T
)를 반환하게 됩니다.
also
는 람다식이 본문을 처리하지만 마지막 표현식이 b
에 할당되는 것이 아닌 person
객체 자신에 할당됩니다. 따라서 b
는 Person
의 객체 person
을 반환하고 새로운 객체 b
가 할당되어 만들어집니다.
fun main() {
data class Person(var name: String, var skills: String)
var person = Person("Kildong", "Kotlin")
val a = person.let {
it.skills = "Android"
"success"
}
println(person)
println("a: $a")
val b = person.also {
it.skills = "Java"
"success"
}
println(person)
println("b: $b")
}
📌 정리
객체의 속성을 전혀 사용하지 않거나 변경하지 않고 사용하는 경우에
also
를 사용합니다. 객체의 데이터 유효성을 확인하거나, 디버그, 로깅 등의 부가적인 목적으로 사용할 때에 적합합니다.
apply
는 함수를 호출하는 객체 T
를 block
으로 전달하고 객체 자체인 this
를 반환합니다.
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
apply
는 블록 안의 코드 수행 결과와 상관없이 this
(객체 T
)를 반환하게 됩니다.
apply
와 also
의 다른 점은 T.()
와 같은 표현에서 람다식이 확장 함수로 처리된다는 점입니다.
또한, also
는 it
을 사용해 멤버에 접근하고 it
을 생략할 수 없지만, apply
는 this
로 받고 this
를 생략할 수 있습니다.
fun main() {
data class Person(var name: String, var skills: String)
var person = Person("Kildong", "Kotlin")
person.apply { this.skills = "Swift" }
println(person)
val returnObj = person.**apply** {
name = "Sean"
skills = "Java"
}
println(person)
print(returnObj)
}
📌 정리
객체의 함수를 사용하지 않고 자기 자신을 다시 반환하기 때문에 주로 객체의 초기화나 변경 시에 사용됩니다.
run
은 두 가지 형태로 선언되어 있습니다.
먼저, 인자가 없는 익명 함수처럼 동작하는 형태로 어떤 객체를 생성하기 위한 명령문을 block
안에 묶음으로써 가독성을 높이는 역할을 합니다.
public inline fun <R> run(block: () -> R): R = return block()
ex)
val num = 10
skills = run {
val level = "Kotlin Level: $num"
level // 마지막 표현식이 반환됨
}
println(skills)
run
의 block
이 독립적으로 사용되어 마지막 표현식을 반환했습니다.
두 번째로는 객체에서 호출하는 형태로, 함수를 호출하는 객체 T
를 block
으로 전달하고 block
의 결괏값 R
을 반환합니다. 어떤 값을 계산할 필요가 있거나 혹은 여러 개의 지역변수의 범위를 제한할 때 사용합니다.
public inline fun <R> T.run(block: T.() -> R): R = return block()
이번에는 block
이 독립적으로 사용됩니다. 이어지는 block
내에서 처리할 작업을 넣어줄 수 있으며 일반 함수와 마찬가지로 값을 반환하지 않거나 특정 값을 반환할 수도 있습니다.
apply
는 this
에 해당하는 객체를 반환하는 반면에 run
은 마지막 표현식이나 혹은 마지막 표현식이 없다면 Unit
을 반환하는 차이점이 있습니다.
fun main() {
data class Person(var name: String, var skills: String)
var person = Person("Kildong", "Kotlin")
val returnObj = person.apply {
name = "Sean"
skills = "Java"
"Success" // 사용되지 않음
}
println(person)
println(returnObj) // Person("Sean", "Java") 출력
val returnObj2 = person.**run** {
this.name = "Dooly"
this.skills = "C#"
"Success"
}
println(person)
println(returnObj2) // Success 출력
}
📌 정리
익명 함수의 형태로는 표현식이 필요한 곳에서 statement를 실행하기 위해 사용하고, 객체를 호출하는 형태로는 객체를 구성하고, 결과를 계산하기 위해 사용합니다.
with
는 인자로 받는 객체를 block
의 receiver
로 전달하며 결괏값을 반환합니다. with
는 run
과 기능이 거의 동일한데, run
의 경우 receiver
가 없지만 with
는 receiver
로 전달할 객체를 처리하므로 객체의 위치가 달라집니다.
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
with
는 매개변수가 두 개이므로 with() {...}
와 같은 형태로 넣어줍니다. with
는 확장 함수 형태가 아니고 단독으로 사용되는 함수이며 세이프 콜(?.)을 지원하지 않기 때문에 let
과 같이 사용되기도 합니다.
또한 let
과 with
의 표현을 병합하면 run
으로도 표현할 수 있습니다.
ex)
fun main() {
data class User(var name: String, var skills: String, var email: String? = null)
val user = User("Kildong", "default")
val result = with(user) {
skills = "Kotlin"
email = "Kildong@example.com"
// 표현식이 없기 때문에 Unit 반환
}
println(user)
println(result)
}
📌 정리
객체에 대해 여러 개의 함수를 호출할 때 그룹화하는 용도로 사용합니다.
Do it! 코틀린 프로그래밍 | 저자 : 황영덕
[Kotlin] 코틀린 let, with, run, apply, also 차이 비교 정리