UFO ET IT

Kotlin에서 run, let, apply 및 with를 사용해야하는 경우의 예

ufoet 2020. 11. 7. 18:16
반응형

Kotlin에서 run, let, apply 및 with를 사용해야하는 경우의 예


각 기능 실행에 대한 좋은 예를 갖고 싶습니다.

이 기사를 읽었 지만 여전히 예제가 부족합니다.


이 모든 기능은 현재 기능 / 변수의 범위를 전환하는 데 사용됩니다. 그들은 함께 속한 것을 한곳에 보관하는 데 사용됩니다 (대부분 초기화).

여기 예시들이 있습니다 :

run -원하는 것을 반환하고 사용 된 변수의 범위를 다시 지정합니다. this

val password: Password = PasswordGenerator().run {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000

       generate()
   }

암호 생성기는 이제 rescoped되어 this우리 따라서 설정할 수 있습니다 seed, hashhashRepetitions변수를 사용하지 않고. generate()의 인스턴스를 반환합니다 Password.

apply비슷하지만 반환됩니다 this.

val generator = PasswordGenerator().apply {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000
   }
val pasword = generator.generate()

이는 Builder 패턴을 대체하고 특정 구성을 재사용하려는 경우 특히 유용합니다.

let-주로 null 검사를 방지하는 데 사용되지만 run. 차이점은 this이전과 동일하며 다음을 사용하여 범위가 변경된 변수에 액세스 한다는 것입니다 it.

val fruitBasket = ...

apple?.let {
  println("adding a ${it.color} apple!")
  fruitBasket.add(it)
}

위의 코드는 null이 아닌 경우에만 사과를 바구니에 추가합니다. 또한 it이제 더 이상 선택 사항 이 아니므로 여기서 NullPointerException이 발생하지 않습니다 (일명 ?.속성에 액세스하는 데 사용할 필요가 없음 ).

also-사용하고 apply싶지만 섀도우를 원하지 않을 때 사용this

class FruitBasket {
    private var weight = 0

    fun addFrom(appleTree: AppleTree) {
        val apple = appleTree.pick().also { apple ->
            this.weight += apple.weight
            add(apple)
        }
        ...
    }
    ...
    fun add(fruit: Fruit) = ...
}

apply여기에서 사용하면 shadow this이므로 과일 바구니가 아닌this.weight 사과를 가리 킵니다 .


참고 : 나는 내 블로그에서 뻔뻔스럽게 예제 가져 왔습니다.


여기같은 몇 가지 기사가 더 있으며 여기 에서 살펴볼 가치가 있습니다.

몇 줄 내에서 더 짧고 간결해야 할 때와 분기 또는 조건문 검사 (예 : null이 아닌 경우이 작업을 수행)를 피하는 것이 가장 중요하다고 생각합니다.

저는이 간단한 차트를 좋아해서 여기에 연결했습니다. 당신은에서 볼 수 세바스티아노 Gottardo에 의해 작성한다.

여기에 이미지 설명 입력

아래의 설명과 함께 제공되는 차트를 참조하십시오.

개념

나는 당신이 그 함수를 호출 할 때 코드 블록 내에서 역할극으로 생각한다 + 당신이 다시 원하는지 (연쇄 호출 함수로 설정하거나 결과 변수로 설정하는 등).

위는 내가 생각하는 것입니다.

개념 예

여기에서 모두에 대한 예를 보겠습니다.

1.) myComputer.apply { }당신이 주인공으로 행동하고 싶다 (당신이 컴퓨터라고 생각하고 싶다), 그리고 당신이 할 수 있도록 (컴퓨터) 당신 자신을 되찾고 싶다는 뜻입니다.

var crashedComputer = myComputer.apply { 
    // you're the computer, you yourself install the apps
    // note: installFancyApps is one of methods of computer
    installFancyApps() 
}.crash()

네, 당신은 앱을 설치하고, 충돌하고, 다른 사람들이 그것을보고 무언가를 할 수 있도록 자신을 참조로 저장했습니다.

2.) myComputer.also {}당신은 당신 컴퓨터 가 아니라는 것을 완전히 확신 하고, 그것으로 무언가를하고 싶어하는 외부인이며, 또한 반환 된 결과로 컴퓨터를 원한다는 것을 의미합니다.

var crashedComputer = myComputer.also { 
    // now your grandpa does something with it
    myGrandpa.installVirusOn(it) 
}.crash()

3.) with(myComputer) { }당신이 주인공 (컴퓨터)이고 그 결과로 자신을 되찾고 싶지 않다는 뜻 입니다.

with(myComputer) {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

4.) myComputer.run { }당신이 주인공 (컴퓨터)이고 그 결과로 자신을 되찾고 싶지 않다는 뜻 입니다.

myComputer.run {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

그러나 다음과 같이 with { }체인 호출을 할 수 있다는 것은 매우 미묘한 의미에서 다릅니다.run { }

myComputer.run {
    installFancyApps()
}.run {
    // computer object isn't passed through here. So you cannot call installFancyApps() here again.
    println("woop!")
}

이것은 run {}확장 기능 때문 이지만 with { }그렇지 않습니다. 따라서 호출 run { }하고 this코드 블록 내부는 호출자 유형의 객체에 반영됩니다. 의 차이점에 대한 훌륭한 설명을 보려면 이것을 볼 수 있습니다 .run {}with {}

5.) myComputer.let { }당신이 컴퓨터를 바라 보는 외부인이고, 컴퓨터 인스턴스가 당신에게 다시 돌아 오도록 신경 쓰지 않고 뭔가를하고 싶어한다는 것을 의미합니다.

myComputer.let {
    myGrandpa.installVirusOn(it)
}

그것을 보는 방법

내가 보는 경향 alsolet외부, 외부 무언가로. 이 두 단어를 말할 때마다 행동을 취하려는 것과 같습니다. let이 컴퓨터에 바이러스를 설치하고 also충돌시킵니다. 그래서 이것은 당신이 배우인지 아닌지의 일부를 결정합니다.

결과 부분의 경우 분명히 있습니다. also그것은 또한 다른 것임을 표현하므로 여전히 객체 자체의 가용성을 유지합니다. 따라서 결과로 반환합니다.

다른 모든 것은 this. 또한 run/with객체 자체를 반환하는 데 관심이 없습니다. 이제 모든 것을 차별화 할 수 있습니다.

저는 때때로 100 % 프로그래밍 / 로직 기반 예제에서 벗어나면 사물을 개념화하는 데 더 나은 위치에 있다고 생각합니다. 그러나 그것은 옳습니다 :)


let, also, apply, takeIf, takeUnless 는 Kotlin의 확장 함수입니다.

이러한 함수를 이해하려면 Kotlin의 확장 함수Lambda 함수 를 이해해야 합니다.

확장 기능 :

확장 함수를 사용하면 클래스 상속없이 클래스에 대한 함수를 만들 수 있습니다.

C # 및 Gosu와 유사한 Kotlin은 클래스에서 상속하거나 Decorator와 같은 모든 유형의 디자인 패턴을 사용하지 않고도 새로운 기능으로 클래스를 확장 할 수있는 기능을 제공합니다. 이는 확장이라는 특수 선언을 통해 수행됩니다. Kotlin은 확장 기능과 확장 속성을 지원합니다.

따라서에서 숫자 만 있는지 알아 보려면 클래스 String를 상속하지 않고 아래와 같은 메서드를 만들 수 있습니다 String.

fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())

위의 확장 기능을 이와 같이 사용할 수 있습니다.

val phoneNumber = "8899665544"
println(phoneNumber.isNumber)

인쇄 true입니다.

Lambda 함수 :

Lambda 함수는 Java의 인터페이스와 같습니다. 그러나 Kotlin에서는 람다 함수를 함수의 매개 변수로 전달할 수 있습니다.

예:

fun String.isNumber(block: () -> Unit): Boolean {
    return if (this.matches("[0-9]+".toRegex())) {
        block()
        true
    } else false
}

블록이 람다 함수이고 매개 변수로 전달된다는 것을 알 수 있습니다. 위의 기능을 이와 같이 사용할 수 있습니다.

val phoneNumber = "8899665544"
    println(phoneNumber.isNumber {
        println("Block executed")
    })

위의 함수는 다음과 같이 인쇄됩니다.

Block executed
true

이제 확장 함수와 Lambda 함수에 대한 아이디어를 얻었기를 바랍니다. 이제 확장 기능으로 하나씩 이동할 수 있습니다.

허락하다

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

위의 기능에서 사용되는 두 가지 유형 T 및 R.

T.let

TString 클래스와 같은 모든 객체가 될 수 있습니다. 이 함수를 어떤 객체로도 호출 할 수 있습니다.

block: (T) -> R

let의 매개 변수에서 위의 lambda 함수를 볼 수 있습니다. 또한 호출하는 객체는 함수의 매개 변수로 전달됩니다. 따라서 함수 내에서 호출하는 클래스 개체를 사용할 수 있습니다. 그런 다음 R(다른 객체)를 반환합니다 .

예:

val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }

위의 예에서 let은 람다 함수의 매개 변수로 String취하고 대가 Pair 를 반환합니다.

같은 방식으로 다른 확장 기능이 작동합니다.

또한

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

확장 함수 also는 호출 클래스를 람다 함수 매개 변수로 취하고 아무것도 반환하지 않습니다.

예:

val phoneNumber = "8899665544"
phoneNumber.also { number ->
    println(number.contains("8"))
    println(number.length)
 }

대다

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

함수와 동일하지만 동일한 호출 객체가 함수로 전달되므로 호출하거나 매개 변수 이름을 호출하지 않고도 함수 및 기타 속성을 사용할 수 있습니다.

예:

val phoneNumber = "8899665544"
phoneNumber.apply { 
    println(contains("8"))
    println(length)
 }

위의 예에서 람다 함수 내에서 직접 호출 된 String 클래스의 함수를 볼 수 있습니다.

takeIf

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null

예:

val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }

위의 예에서 number의 문자열있을 것이다 phoneNumber는 일치 만 regex. 그렇지 않으면 null.

takeUnless

public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null

takeIf의 반대입니다.

예:

val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }

numberphoneNumber일치하지 않는 경우에만 문자열이 있습니다 regex. 그렇지 않으면 null.

여기에서 유용한 유사한 답변을 볼 수 있습니다 .kotlin의 차이점도 Kotlin에서 적용, 사용, 사용, takeIf 및 takeUnless


내 경험에 따르면 이러한 함수는 성능 차이가없는 인라인 구문 설탕이므로 항상 람다에 최소한의 코드를 작성해야하는 것을 선택해야합니다.

이렇게하려면 먼저 람다가 결과를 반환할지 ( run/ 선택 let) 또는 객체 자체 ( apply/ 선택) 를 반환할지 결정합니다 also. 그런 다음 람다가 단일 표현식 인 경우 대부분의 경우 해당 표현식과 동일한 블록 함수 유형을 가진 항목을 선택합니다. 수신자 표현식 일 this때는 생략 할 수 있고 매개 변수 표현식 일 it때는 this다음 보다 짧기 때문입니다 .

val a: Type = ...

fun Type.receiverFunction(...): ReturnType { ... }
a.run/*apply*/ { receiverFunction(...) } // shorter because "this" can be omitted
a.let/*also*/ { it.receiverFunction(...) } // longer

fun parameterFunction(parameter: Type, ...): ReturnType { ... }
a.run/*apply*/ { parameterFunction(this, ...) } // longer
a.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"

그러나 람다가 이들의 혼합으로 구성 될 때 상황에 더 잘 맞는 것을 선택하거나 더 편안하게 느끼는 것은 귀하에게 달려 있습니다.

또한 해체가 필요할 때 매개 변수 블록 기능이있는 것을 사용하십시오.

val pair: Pair<TypeA, TypeB> = ...

pair.run/*apply*/ {
    val (first, second) = this
    ...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter

다음은 Java 개발자를위한 Coursera Kotlin에 대한 JetBrains의 공식 Kotlin 과정에서 이러한 모든 기능을 간략하게 비교 한 것입니다 .차이 표 단순화 된 구현

참고 URL : https://stackoverflow.com/questions/45977011/example-of-when-should-we-use-run-let-apply-also-and-with-on-kotlin

반응형