코틀린 표준 라이브러리에서 제공하는 확장함수로, 객체의 컨텍스트 내에서 실행 가능한 코드 블록을 만들어 준다. 호출 시 임시 범위가 생성되며, 이 범위 안에서는 이름 없이 객체에 접근이 가능하다.
수신 객체를 명시하지 않고, 람다의 본문 안에서 해당 객체의 메서드를 호출할 수 있게 하는 람다 표현식
✅ 수신 객체(receiver) : 확장 함수가 호출되는 대상이 되는 값(객체)
✅ 수신 객체 타입(receiver type) : 해당 객체의 클래스 이름
inline fun <T, R> with(receiver: T, block: T.() -> R): R { return receiver.block() }
- 수신 객체 : receiver 매개변수로 전달된 T 타입의 객체
- 수신 객체 타입 : T
- 수신 객체 지정 람다 : block
- T 타입의 수신 객체를 받아서 해당 객체의 확장 함수인 block: T.() -> R를 실행하고 그 결과인 R을 반환
코틀린 범위 지정 함수 | |||
---|---|---|---|
수신 객체 | 확장 함수로 호출 | 함수의 인자 | |
this (생략 가능) | apply | run | with |
it (생략 불가능) | also | let | |
return | 수신 객체 | 람다 식의 마지막 행 |
수신 객체를 it로 받아서 람다 내부에서 사용하며, 람다의 마지막 표현식을 결과로 반환한다.
null이 아닌 객체에 대한 작업을 수행하거나, 지역 변수를 명시적으로 표현할 때 주로 사용한다.
class Person(val name: String, val age: Int, val gender: Boolean)
fun main() {
var person: Person? = Person("영희", 10, true)
// 단일 지역 변수(nonNullPerson)의 범위를 제한하는 경우
val age = person?.let { nonNullPerson ->
nonNullPerson.age
}
println(age) // 10
}
val driversLicence: Licence? = getNullablePerson()?.let {
licenceService.getDriversLicence(it)
}
// 추천하지 않는 코드
fun process(str: String?) {
str?.let { ... }
}
// 자바로 디컴파일된 코드
public final void process(@Nullable String str) {
if (str != null) {
boolean var4 = false;
...
}
}
// 추천하는 코드
fun process(str: String?) {
if (str != null) { ... }
}
// 자바로 디컴파일된 코드
public final void process(@Nullable String str) {
if (str != null) { ... }
}
private var str: String? = null
fun process() {
str?.let { ... }
}
// 추천하지 않는 코드
var javaScriptEnabled = false
var databaseEnabled = false
webviewSetting?.run {
javaScriptEnabled = javaScriptEnabled
databaseEnabled = databaseEnabled
}
// let을 사용하여 수정한 코드
var javaScriptEnabled = false
var databaseEnalbed = false
webviewSetting?.let {
javaScripeEnabled = it.javaScriptEnabled
databaseEnabled = it.databaseEnabled
}
// 추천하지 않는 코드
fun process(string: String?): List? {
return string?.asIterable()?.distinct()?.sorted()
}
// let을 사용하여 수정한 코드
fun process(string: String?): List? {
return string?.let {
it.asIterable().distinct().sorted()
}
}
// 추천하지 않는 코드
fun process(stringList: List<String>?, removeString: String): Int? {
var count: Int? = null
if (stringList != null) {
count = stringList.filterNot { it == removeString }
.sumOf { it.length }
}
return count
}
// let을 사용하여 수정한 코드
fun process(stringList: List<String>?, removeString: String): Int? {
return stringList?.let { list ->
list.filterNot { it == removeString }
.sumOf { it.length }
}
}
수신 객체를 this로 받아서 람다 내부에서 사용하며, 람다의 마지막 표현식을 결과로 반환한다.
객체의 초기화와 반환값을 동시에 처리할 때 유용하다.
class User(val name: String, val age: Int, val gender: Boolean)
fun main() {
val kid = User("아이", 4, false)
val kidAge = kid.run {
age
}
println(kidAge) // 4
}
// 수신 객체 없이도 동작할 수 있다.
fun main() {
val kid = User("아이", 4, false)
val kidAge = run {
kid.age
}
println(kidAge) // 4
}
// 여러 개의 지역 변수의 범위를 제한할 수 있다.
val inserted: Boolean = run {
// person 과 personDao 의 범위를 제한한다.
val person: Person = getPerson()
val personDao: PersonDao = getPersonDao()
// 수행 결과를 반환한다.
personDao.insert(person)
}
수신 객체를 this로 받아서 람다 내부에서 사용하며, 수신 객체 자체를 반환한다.
객체의 초기화와 그 객체를 반환하려 할 때 주로 사용한다.
class User(
val name: String,
val age: Int,
val gender: Boolean,
var hasGalsses: Boolean = true
)
fun main() {
val kid = User("아이", 4, false)
val kidName = kid.apply {
name
}
println(kidName) // 수신객체 자기 자신(kid) 반환
val female = User("슬기", 20, true, true)
val femaleValue = female.apply {
hasGalsses = false
}
print(femaleValue.hasGalsses) // false
}
수신 객체를 it로 받아서 람다 내부에서 사용하며, 수신 객체 자체를 반환한다.
객체의 초기화 이후 추가적인 작업을 수행하면서 그 객체를 계속 사용하려 할 때(로깅 등) 유용하다.
data class User(
val name: String,
val age: Int,
val gender: Boolean,
var hasGalsses: Boolean = true
)
fun main() {
val male = User("민수", 17, false, true)
val maleValue = male.also {
println(it.name)
}.toString()
println(maleValue)
}
// 민수
// User(name=민수, age=17, gender=false, hasGalsses=true)
class Book(val author: Person) {
init {
requireNotNull(author.age)
print(author.name)
}
}
class Book(author: Person) {
val author = author.also {
requireNotNull(it.age)
print(it.name)
}
}
run 함수와 비슷하지만, 함수를 호출하는 대상이 아닌 첫 번째 인자로 받는 객체를 this로 받아서 람다 내부에서 사용하며, 람다의 마지막 표현식을 결과로 반환한다.
객체의 초기화와 람다 리턴 값이 필요 없을 때 주로 사용한다.
class User(
val name: String,
val age: Int,
val gender: Boolean,
var hasGalsses: Boolean = true,
)
fun main() {
val male = User("민수", 17, false, true)
val result = with(male) {
hasGalsses = false
true
}
print(result) // true
}
this : 수신 객체를 가리키는 키워드로, 객체의 멤버에 직접 접근할 수 있으며, 객체의 속성을 초기화하거나 설정하는 등의 작업을 쉽게 수행할 수 있다.
it : 람다에서 단일 인자를 가리키는 키워드로, 수신 객체의 프로퍼티나 함수에 직접 접근하지 않고, 해당 객체 자체에 대한 참조를 사용한다.
Q1. 직접 참조와 간접 참조의 차이점 : 객체의 멤버에 직접 접근하면 코드에서 명시적으로 수신 객체를 참조하므로, 해당 객체에 대한 작업이 명확하게 드러난다. 간접적으로 수신 객체의 멤버에 접근하면 코드가 보다 간결해진다. 특히, 람다 또는 함수에서 수신 객체의 멤버를 적은 횟수로 사용하는 경우에 더 적합하다.
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T?
= if (predicate(this)) this else null
return if(x.isValid()) x else null // 전
return x.takeIf { it.isValid() } // 후
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T?
= if (!predicate(this)) this else null
return if(!x.isError()) x else null // 전
return x.takeUnless { it.isError } // 후
// 수정 전
return if(x.isValid()) doWorkWith(x) else null
// 수정 후
return doWorkWith(x).takeIf { x.isValid() }
1. 연산의 순서
2. 초과 연산
3. 부수 효과
1. 객체가 식이 아닐 때
data class Person(val name: String, val age: Int)
fun main() {
val person = Person("Alice", 25)
val result = person.takeIf { it.age >= 18 }
if (result != null) {
println("Person is valid: $result")
} else {
println("Person is not valid")
}
}
2. 기타
// 수정 전
return if (evensOnly && x % 2 == 0) x else null
// 수정 후
return x.takeIf { evensOnly && x % 2 == 0}
// 수정 전
return if (someString.isNotBlank()) {
someMoreWork(someString)
} else {
null
}
// 수정 후
return someString.takeIf { it.isNotBlank() }?.let { someMoreWork(it) }