코틀린의 apply, with, let, also, run과 같은 함수들을 '범위 지정 함수'라고 합니다. 각각의 쓰임새에 대해서 알아 보겠습니다.
1. 정의
먼저, apply, with, let, also, run의 정의를 확인 하겠습니다.
// with() 정의
inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
// also() 정의
inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
// apply() 정의
inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
// let() 정의
inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
// run() 정의
inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
2. 간단한 사용 예시
run |
val r: R = T( ).run { this.foo(); this.toR() } |
with |
val r: R = with(T( )) { this.foo(); this.toR() } |
apply |
val t: T = T( ).apply { this.foo() } |
also |
val t: T = T( ).also { it.foo() } |
let |
val r: R = T( ).let { it.foo(); it.toR() } |
3. apply 사용 규칙
수신 객체 람다 내부에서 수신 객체의 함수를 사용하지 않고, 자신을 다시 반환 하려는 경우에 apply 를 사용합니다. 수신 객체의 프로퍼티만을 사용하여 객체 초기화를 하는 경우가 대표적인 예 입니다.
// apply()를 사용하지 않는 경우
val killer = Person()
killer.name = "Killer"
killer.age = 21
// apply()를 사용하는 경우
val killer = Person().apply {
// apply의 블록 에서는 오직 프로퍼티만 사용합니다!
name = "Killer"
age = 21
}
4. also 사용 규칙
람다 내부에서 수신 객체를 전혀 사용 하지 않거나, 수신 객체의 속성을 변경하지 않고 사용하는 경우 also 를 사용합니다. also는 apply와 마찬가지로 수신 객체를 반환 합니다. 예를 들어, 객체의 사이드 이팩트를 확인하거나, 데이터를 할당하기 전에 해당 데이터의 유효성을 검사 할 때 매우 유용합니다.
//also()를 사용하지 않는 경우
class Book(val author: Person) {
init {
requireNotNull(author.age)
print(author.name)
}
}
//also()를 사용하는 경우
class Book(author: Person) {
val author = author.also {
requireNotNull(it.age)
print(it.name)
}
}
5. let 사용 규칙
- 지정된 값이 null 이 아니면서, 코드를 실행해야 하는 경우
- Nullable객체를 다른 Nullable객체로 변환하는 경우
- 해당 객체를 람다 내부의 지역 변수로 범위를 제한 하는 경우
//let()을 사용하지 않는 경우
val person: Person? = getPromotablePerson()
if (person != null) {
promote(person)
}
val driver: Person? = getDriver()
val driversLicence: Licence? = if (driver == null) null else
licenceService.getDriversLicence(it)
val person: Person = getPerson()
val personDao: PersonDao = getPersonDao()
personDao.insert(person)
//let()을 사용하는 경우
getNullablePerson()?.let {
// null 이 아닐때만 실행됩니다.
promote(it)
}
val driversLicence: Licence? = getNullablePerson()?.let {
// nullable personal객체를 nullable driversLicence 객체로 변경합니다.
licenceService.getDriversLicence(it)
}
val person: Person = getPerson()
getPersonDao().let { dao ->
// 변수 dao 의 범위는 이 블록 안 으로 제한 됩니다.
dao.insert(person)
}
6. with 사용 규칙
수신 객체가 Non-Nullable 이고, 반환값이 필요하지 않은 경우 with를 사용합니다.
//with()를 사용하지 않는 경우
val person: Person = getPerson()
print(person.name)
print(person.age)
//with()를 사용하는 경우
val person: Person = getPerson()
with(person) {
print(name)
print(age)
}
7. run 사용 규칙
- 어떤 값을 계산할 필요가 있거나, 여러개의 지역 변수의 범위를 제한하는 경우.
- 매개 변수로 전달된 명시적 수신객체를 암시적 수신 객체로 변환 하는 경우.
//run()을 사용하지 않는 경우
val person: Person = getPerson()
val personDao: PersonDao = getPersonDao()
val inserted: Boolean = personDao.insert(person)
fun printAge(person: Person) = {
print(person.age)
}
//run()을 사용하는 경우
val inserted: Boolean = run {
// person 과 personDao 의 범위를 제한 합니다.
val person: Person = getPerson()
val personDao: PersonDao = getPersonDao()
// 수행 결과를 반환 합니다.
personDao.insert(person)
}
fun printAge(person: Person) = person.run {
// person 을 수신객체로 변환하여 age 값을 사용합니다.
print(age)
}
8. 5개의 범위 지정 함수 결합
범위 지정 함수가 중첩되면 코드의 가독성이 떨어집니다. 따라서, 중첩을 피하는 것이 좋습니다.
하지만, 체인 형식으로 적절히 조합 하여 사용하면, 코드의 가독성이 향상 됩니다. 다음은 체인 형식으로 잘 사용된 예 입니다.
private fun insert(user: User) = SqlBuilder().apply {
append("INSERT INTO user (email, name, age) VALUES ")
append("(?", user.email)
append(",?", user.name)
append(",?)", user.age)
}.also {
print("Executing SQL update: $it.")
}.run {
jdbc.update(this) > 0
}
- SQL 준비 - apply
- SQL 로그 출력 - also
- SQL 실행 및 boolean 반환 - run
위와 같이, 범위 지정 함수를 체인 형식으로 적절히 분리 하여 사용 할수 있습니다.
'코틀린' 카테고리의 다른 글
코틀린 setter, getter (0) | 2021.02.20 |
---|---|
Kotlin Unit Test 하는법 (0) | 2020.12.10 |
Kotlin 코루틴(Coroutine) (0) | 2020.12.07 |
코틀린 SAM 변환이 왜 되지 않을까? (0) | 2020.11.25 |
코틀린 6편 (lateinit) (0) | 2020.11.13 |