ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [kotlin] 코틀린 let, run, with, apply, also 함수 비교 및 상황별 함수 선택
    Android/Kotlin 2020. 12. 26. 23:59

    코틀린에서 기본적으로 제공하는apply, let, run, with, also, takeIf 와 사용 예를 알아보겠다.
    코틀린의 표준 함수는 내부적으로 확장함수(extention function) 이다
    각함수별 특징과 상황별 선택요령에 대해서 설명 해볼려고 한다.


    함수별 참조방식, 리턴값 비교

    Function Object reference Return value
    let it Lambda result
    run this Lambda result
    with this Lambda result
    apply this Context object
    also it Context object

    this or it 에 따른 참조방식

    • 각 스코프 함수는 컨텍스트 객체에 접근하는 두 가지 방법 중 하나를 사용한다. lambda 리시버 (this) or 람다 인수(it)
      각자 아래와 같은 예로 접근하여 사용할 수 있다.

      • this : 수신자 객체의 멤버에 액세스할 때 코드를 생략할 수 있다, 주로 객체 멤버에서 작동하는 labdas에 권장(ex. 함수를 호출하거나 속성을 할당할때), 하지만 람다 함수 내에 로직이 많을경우 멤버에 접근하는 것인지 외부 변수인지 헷갈릴수 있으므로 이점을 유의해서 사용해야 할 듯 하다.
      • it : 객체 함수 또는 속성을 호출할 때 this와 같이 암시적으로 사용할 수 있는 객체가 없다. 따라서 객체 함수 호출에서 대부분 인수로 사용될 때 사용하면 좋을 듯 하다.
      fun main() {
        val str = "Hello"
        // this
        str.run {
            println("The receiver string length: $length")
            //println("The receiver string length: ${this.length}") // does the same
        }
      
        // it
        str.let {
            println("The receiver string's length is ${it.length}")
        }
      }

    Return Value

    1. Context object

    • 호출된 객체 스스로를 반환한다. 따라서, 객체에 대해 연속적으로 호출할 수 있다.

        val numberList = mutableListOf<Double>()
        numberList.also { println("Populating the list") }
        .apply {
            add(2.71)
            add(3.14)
            add(1.0)
        }
        .also { println("Sorting the list") }
        .sort()
    • 또한 컨텍스트 객체를 반환하는 함수의 반환 문에도 사용할 수 있다.
     fun getRandomInt(): Int {
      return Random.nextInt(100).also {
          writeToLog("getRandomInt() generated value $it")
        }
      }
    
      val i = getRandomInt()

     

    2. Lambda result

    • 변수에 함수 실행 결과를 할당하고 결과에 대한 작업을 체인화하는 등의 자겁을 수핼할때 사용할 수 있다.

        val numbers = mutableListOf("one", "two", "three")
        val countEndsWithE = numbers.run { 
            add("four")
            add("five")
            count { it.endsWith("e") }
        }
        println("There are $countEndsWithE elements that end with e.")
    • 반환 값을 무시하고 범위함수를 사용하여 변수에 대한 임시 범위를 생성할 수 있다.

      val numbers = mutableListOf("one", "two", "three")
      with(numbers) {
          val firstItem = first()
          val lastItem = last()        
          println("First item: $firstItem, last item: $lastItem")
      }

    각 함수별 설명 및 예제

    1. let

    • 함수의 인자로 전달된 람다를 실행한 후 결과를 반환한다.

        // str이 null이 아닐때 길이 return 
        val length = str?.let { 
          it.length
        }
        // 두 작업에 대한 결과를 출력하고자 할때 별도 변수에 저장하지 않고 바로 수행 가능
        val numbers = mutableListOf("one", "two", "three", "four", "five")
        numbers.map { it.length }.filter { it > 3 }.let { 
            println(it)
            // and more function calls if needed
        } 

    2. with

    • 람다 결과를 제공하지 않고 컨텍스트 객체의 함수를 호출하는 경우 사용하기 좋다.
    • 코드에서 "이 객체를 사용하여 다음 작업을 수행하십시오." 라고 해석하면 된다.
        val numbers = mutableListOf("one", "two", "three")
        with(numbers) {
            println("'with' is called with argument $this")
            println("It contains $size elements")
        }

    3. run

    • with와 동일한 작업을 수행하지만 컨텍스트 객체의 확장 함수로 let을 호출한다.

    • 람다가 객체 초기화 및 반환 값 계산을 모두 포함하는 경우에 유용하다.

        val service = MultiportService("https://example.kotlinlang.org", 80)
      
        val result = service.run {
            port = 8080
            query(prepareRequest() + " to port $port")
        }
      
        // the same code written with let() function:
        val letResult = service.let {
            it.port = 8080
            it.query(it.prepareRequest() + " to port ${it.port}")
        }
    • receiver object의 실행을 호출하는 것 외에도 확장 기능이 아닌 기능으로 사용할 수 있다. 필요한 여러 문의 블록을 실행할 수 있다.

            val hexNumberRegex = run {
            val digits = "0-9"
            val hexDigits = "A-Fa-f"
            val sign = "+-"
      
            Regex("[$sign]?[$digits$hexDigits]+")
        }
      
        for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
            println(match.value)
        }

       

    4. apply

    • 이 함수는 구성 함수라고 생각할 수 있는데 수신자 객체를 구성하기 위해 apply의 람다에 포함된 수신자 함수들을 연속적으로 호출할 수 있다.

      // 객체 멤버변수의 값을 셋팅할때 간결하게 구성할 수 있다.
      val adam = Person("Adam").apply {
          age = 32                // adam.age = 32
          city = "London"            // adam.city = "London"
      }
      println(adam)
    • 또한 컨텍스트 객체를 반환하는 함수의 반환 문에도 상용 할 수 있다.

        fun getRandomInt(): Int {
        return Random.nextInt(100).also {
            writeToLog("getRandomInt() generated value $it")
          }
        }
      
        val i = getRandomInt()

       

       

    5. also

    • 컨텍스트 객체를 인수로 사용하는 일부 작업을 수행 하는데 유용하다. 속성 및 기능이 아닌 참조가 필요한 작업이나 외부 범위에서 이 참조를 음영 처리하지 않으려는 경우 사용한다.

      val numbers = mutableListOf("one", "two", "three")
      numbers
        .also { println("The list elements before adding new one: $it") }
        .add("four")

    케이스별 함수 선택

    • Null이 아닌 객체에서 람다함수 실행 : let

    • 로컬 범위에서 변수로 표현식 소개 : let

    • 객체 구성(object configuration) : apply

    • 객체 구성 및 결과 계산 : run

    • 식이 필요한 실행 문 : non-extension run

    • 추가 효과 : also

    • 객체에 대한 그룹화 함수 호출 : with

    * 범위 기능은 코드를 보다 간결하게 만드는 한 방법이지만, 과도하게 사용하면 가독성이 저하되고 오류가 발생할 수 있다.

    takeIf and takeUnless

    • 객체 상태에 대한 검사를 호출 체인에 포함시킬 수 있다.

    • takeIf 조건이 일치하는경우 현재 객체를 반환하고 그렇지 않으면 null을 반환한다. 따라서 단일 객체에 대한 필터링 기능을 한다.

    • takeUnless는 일치하지 않으면 객체를 반환하고, 일치하면 null을 반환한다.

      val number = Random.nextInt(100)
      
      val evenOrNull = number.takeIf { it % 2 == 0 }
      val oddOrNull = number.takeUnless { it % 2 == 0 }
      println("even: $evenOrNull, odd: $oddOrNull")
    • takeIf 및 takeUnless 이후 다른 기능을 체인할 때는 반환 값이 null이므로 null 체크 또는 Safe Call(?)을 수행해야 한다.

      val str = "Hello"
      val caps = str.takeIf { it.isNotEmpty() }?.toUpperCase()
      //val caps = str.takeIf { it.isNotEmpty() }.toUpperCase() //compilation error
      println(caps)
    • 스코프 기능과 함꼐 사용하기 특히 유용하다..

      fun displaySubstringPosition(input: String, sub: String) {
          input.indexOf(sub).takeIf { it >= 0 }?.let {
              println("The substring $sub is found in $input.")
              println("Its start position is $it.")
          }
      }
      
      displaySubstringPosition("010000011", "11")
      displaySubstringPosition("010000011", "12")

       

    • 표준 라이브러리 기능이 없는 동일한 기능은 다음과 같다.

      fun displaySubstringPosition(input: String, sub: String) {
        val index = input.indexOf(sub)
        if (index >= 0) {
            println("The substring $sub is found in $input.")
            println("Its start position is $index.")
        }
      }
      
      displaySubstringPosition("010000011", "11")
      displaySubstringPosition("010000011", "12")

    참고사이트

    댓글

Designed by Tistory.