[코틀린] 많이 쓰이는 표준함수 let, also, apply, run, with 의 차이

에짱·2021년 9월 12일
1
post-thumbnail

코틀린의 표준 라이브러리의 표준 함수들을 살펴보고 안드로이드에서의 예시들을 살펴봅시다! 이들을 활용하면 코드를 훨씬 간결하게 만들 수 있답니다!

let, apply, with, also, run 의 가장 큰 차이는 람다식의 접근 방법과 반환 방법입니다.

let() 함수

public line fun <T, R> T.let(block: (T) -> R) : R { ... return block(this) }
  • 제너릭의 확장 함수로 어디든 적용할 수 있습니다.
  • 본문의 this 는 객체 T 를 가리키며 람다식 결과 부분을 그대로 반환한다는 것을 의미합니다.
  • 메서드 체이닝에 활용 가능합니다.

nullable 객체에 사용

val firstName : String?
val lastName : String

if(firstName != null) print("$firstName $lastName")
else print("$lastName")

//let 사용
firstName?.let { print("$firstName $lastName") } ?: print("$lastName")

android example

val feedDetailRetrofitInterface = ApplicationClass.retrofit.create(FeedDetailRetrofitInterface::class.java)
feedDetailRetrofitInterface.postLike(LikeRequest(contentsNo))?.let {
    if (it.isSuccessful) it.body()?.let { res ->
      if (res.isSuccess) view.onSuccessPostLike(itemPosition)
      else view.onFailPostLike()
    } else view.onFailPostLike()
}

저는 retrofit 통신에서 특히 간결하게 let 으로 위와 처리합니다ㅎㅎ

also() 함수

public inline fun <T> T.also(block : (T) -> Unit) : T { block(this); return this }
public line fun <T, R> T.let(block: (T) -> R) : R { ... return block(this) }

also 함수는 let 과 유사해보이지만, 반환값이 다릅니다. also 함수는 블록 안의 코드 수행 결과와 상관없이 T인 객체 this를 반환합니다.

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.sills = "Java"
      "success" //마지막 문장은 사용되지 않음
  }
  println(person)
  println("b : $b")
}

//Person(name=Kildong, skills=Android)
//a: success
//Person(name=Kildong, skills=Java)
//b: Person(name=Kildong, slills=Java)

답이 여러분 예상과 같았나요?
also 는 람다식 본문을 처리하긴 하지만, 마지막 표현식이 b에 할당되는 것이 아니라, person 객체가 할당됩니다.

android example

 noTaskAddView = (findViewById<TextView>(R.id.noTasksAdd)).also {
                it.setOnClickListener { showAddTask() }
            }

안드로이드에서는 뷰 객체를 초기화 할 때, setOnClickListener 도 함께 초기화해서 변수에 할당하고 싶을 때, also 는 반환값이 객체 T 자체이기 때문에 이렇게 사용하면 되겠죠?ㅎㅎ

apply() 함수

public inline fun <T> T.also(block : (T) -> Unit) : T { block(this); return this }
public line fun <T, R> T.let(block: (T) -> R) : R { ... return block(this) }
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

그 중 apply() 함수와 also() 함수는 둘 다 객체 T를 block 으로 전달하고 객체 자체인 this 를 반환합니다. 하지만 apply() 는 T.()와 같은 표현에서 람다식이 확장 함수로 처리된다는 점이 다릅니다.

fun main() {
  data class Person(var name: String, var skills: String)
  var person = Person("Ejjjang", "Kotlin")
  person.also { it.skills = "Java" }
  person.apply { skills = "Swift" }
}

apply() 가 also() 함수와 다른 점은 객체를 넘겨 받는 방식입니다.
also() 함수는 it 을 통해 멤버에 접근합니다. 하지만 apply() 에서는 함수 타입에 리시버가 있기 때문에 this 를 생략하고 멤버 이름만 사용하고 있습니다.

apply() 함수는 위의 예시처럼 특정 객체를 생성하면서 함께 호출해야 하는 초기화 코드가 있는 경우 많이 사용됩니다.

android example

val markerOptions = MarkerOptions().apply {
            position(positionLatLng)
            title(searchResult.buildingName)
            snippet(searchResult.fullAddress)
        }

위에서 말한 것 처럼 객체를 생성하면서 여러 번 멤버에 접근해야 할 때 유용하게 쓰입니다.

run() 함수

public inline fun <R> run(block: () -> R):R = return block()
public inline fun <T, R> T.run(block: T.() -> R):R = return block()

run() 함수는 인자가 없는 익명 함수처럼 동작하는 형태와 객체에서 호출하는 형태, 2가지로 사용할 수 있습니다.

val returnObj = person.run {
  this.name = "Dooly"
  this.skills = "C++"
  "SUCCESS"
}

여기서 returnObj 에는 마지막 표현식인 "SUCCESS" 가 할당이 됩니다. 마지막 표현식이 없으면 Unit 이 반환됩니다.

with() 함수

public inline fun <T, R> with(receiver: T, block: T.() -> R):R = receiver.block()

with() 함수는 run() 함수와 거의 동일한데, 인자로 받는 객체를 이어지는 block 의 receiver(T.) 로 전달하며 결괏값을 반환합니다.

  • with() 는 확장함수 형태가 아니고 단독으로 사용되는 함수입니다.
  • 또한 세이프 콜(?.)을 지원하지 않기 때문에 let() 과 같이 사용되기도 합니다.
supportActionBar?.let {
  with(it) {
    setDisplyHomeAsUpEnabled(true)
    setHomeAsUpIndicator(R.drawable.ic_clear_white)
}

여기서 재미있는 것은 let() 함수와 with() 함수를 병합하면 run() 함수로 다음과 같이 표현할 수 있다는 것입니다.

supportActionBar?.run {
    setDisplyHomeAsUpEnabled(true)
    setHomeAsUpIndicator(R.drawable.ic_clear_white)
}

android example

private fun bindViews() = with(binding) {
        fabMyLocation.setOnClickListener {
            getMyLocation()
        }
    }

binding을 초기화 해준 경우 binding.fabMyLocation 해줄 필요없이 바로 fabMyLocation 에 접근할 수 있겠죠. 여러 view 를 건드려야 할 때 유용합니다.

정리

안드로이드 개발을 하다보면 자신만의 표준함수 쓰는 곳?! 이 생기는 것 같더라구요 ㅋㅋ 다른 사람들이 좋은 곳에 사용하는 것을 보면 저도 따라서 써보고 했는데 제 코드가 더욱 간결해지고 깔끔해져서 표준 함수를 더 열심히 쓰고 싶은ㅋㅋ 마음이 들더라구요!
그러나!! 책에도 나와있었지만 너무 남용하는 것은 오히려 가독성을 떨어뜨릴 수 있으니 조심해야겠지용?! ㅎㅎ

아직 run 을 제대로 활용해본 적이 없는데 앞으로 좋은 활용처를 찾으면 example 부분에 추가할 수 있도록 하겠습니다!ㅎㅎ

참고문헌

도서 <Do it! 코틀린 프로그래밍> http://www.yes24.com/Product/Goods/74035266?OzSrank=1

profile
지금 여기. Here and Now

0개의 댓글