이제 이 다섯 가지 함수가 서로 무엇이 다른지 알게되었습니다.
그러나 여전히 이 함수들은 비슷해 보이고, 실제로도 서로 많은 케이스에서 교환하여 사용이 가능하므로 어느 함수를 어디에 사용해야 하는지 판단하기는 어렵습니다.
코틀린 공식 문서에는 이 다섯 가지 함수에 대한 몇 가지 모범 사례와 규칙이 있습니다.
이러한 규칙을 학습하면 더 많은 관용구 코드를 작성할 수 있으며 다른 개발자 코드의 의도를 더 빨리 이해하는 데 도움이 됩니다.
수신 객체 람다 내부에서 수신 객체의 함수를 사용하지 않고 수신 객체 자신을 다시 반환 하려는 경우에 apply 를 사용합니다.
수신 객체 의 프로퍼티 만을 사용하는 대표적인 경우가 객체의 초기화 이며, 이곳에 apply 를 사용합니다.
val peter = Person().apply {
// apply 의 블록 에서는 오직 프로퍼티 만 사용합니다!
name = "Peter"
age = 18
}
apply 를 사용하지 않는 동일한 코드는 다음과 같습니다.
val clark = Person()
clark.name = "Clark"
clark.age = 18
수신 객체 람다가 전달된 수신 객체를 전혀 사용 하지 않거나 수신 객체의 속성을 변경하지 않고 사용하는 경우 also 를 사용합니다.
also 는 apply 와 마찬가지로 수신 객체를 반환 하므로 블록 함수가 다른 값을 반환 해야하는 경우에는 also 를 사용할수 없습니다.
예를 들자면, 객체의 사이드 이팩트를 확인하거나 수신 객체의 프로퍼티에 데이터를 할당하기 전에 해당 데이터의 유효성을 검사 할 때 매우 유용합니다.
class Book(author: Person) {
val author = author.also {
requireNotNull(it.age)
print(it.name)
}
}
also 를 사용하지 않는 동일한 코드는 다음과 같습니다.
class Book(val author: Person) {
init {
requireNotNull(author.age)
print(author.name)
}
}
다음과 같은 경우에 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)
}
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)
Non-nullable (Null 이 될수 없는) 수신 객체 이고 결과가 필요하지 않은 경우에만 with 를 사용합니다.
val person: Person = getPerson()
with(person) {
print(name)
print(age)
}
with 를 사용하지 않는 동일한 코드는 다음과 같습니다.
val person: Person = getPerson()
print(person.name)
print(person.age)
어떤 값을 계산할 필요가 있거나 여러개의 지역 변수의 범위를 제한하려면 run 을 사용합니다.
매개 변수로 전달된 명시적 수신객체 를 암시적 수신 객체로 변환 할때 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)
}
run 을 사용하지 않는 동일한 코드는 다음과 같습니다.
val person: Person = getPerson()
val personDao: PersonDao = getPersonDao()
val inserted: Boolean = personDao.insert(person)fun printAge(person: Person) = {
print(person.age)
}
코드 가독성을 향상시키기 위해 범위 지정 기능을 분리하여 어떻게 사용할 수 있는지 보여주었습니다.
하나의 코드 블록 내에서 여러 범위 지정 함수를 중첩 하려는 경우가 종종 있습니다. 그러나 범위 지정 함수가 중첩되면 코드의 가독성이 떨어지고 파악하기 어려워 집니다.
원칙적으로 중첩은 하지 않는 것이 좋습니다.
수신객체 지정 람다 에 수신 객체가 암시적으로 전달되는 apply, run, with 는 중첩하지 마십시오.
이 함수들은 수신 객체를 this 또는 생략하여 사용하며, 수신객체의 이름을 다르게 지정할수 없기 때문에 중첩될 경우 혼동 하기 쉬워집니다.
also 와 let 을 중첩 해야만 할때는 암시적 수신 객체 를 가르키는 매개 변수 인 it 을 사용하지 마십시오. 대신 명시적인 이름을 제공하여 코드 상의 이름이 혼동되지 않도록 해야 합니다.
출처 :
https://medium.com/@limgyumin/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%9D%98-apply-with-let-also-run-%EC%9D%80-%EC%96%B8%EC%A0%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80-4a517292df29
https://inma.tistory.com/166