Kotlin의 다중 변수 렛


127

kotlin에서 여러 nullable 변수에 대해 여러 let을 연결하는 방법이 있습니까?

fun example(first: String?, second: String?) {
    first?.let {
        second?.let {
            // Do something just if both are != null
        }
    }
}

내 말은, 다음과 같습니다.

fun example(first: String?, second: String?) {
    first?.let && second?.let { 
        // Do something just if both are != null
    }
}

1
2 개가 아닌 N 개 항목을 원하십니까? 모든 항목에 동일한 유형이 필요한가요, 아니면 다른 유형이 필요한가요? 모든 값을 함수, 목록 또는 개별 매개 변수로 전달해야합니까? 반환 값은 단일 항목이어야합니까 아니면 입력과 동일한 수의 항목 그룹이어야합니까?
Jayson Minard

나는 모든 논쟁이 필요합니다.이 경우에는 두 가지가 될 수 있지만 더 많은 것을 위해 이것을 수행하는 방법을 알고 싶었습니다.
Daniel Gomez Rico

아래 답변과 다른 것을 찾고 있습니까? 그렇다면 원하는 차이점이 무엇인지 설명하십시오.
Jayson Minard

두 번째 let 블록 내에서 첫 번째 "it"을 참조하는 방법은 무엇입니까?
Javier Mendonça

답변:


48

여기에 관심이 있다면 이것을 해결하는 두 가지 기능이 있습니다.

inline fun <T: Any> guardLet(vararg elements: T?, closure: () -> Nothing): List<T> {
    return if (elements.all { it != null }) {
        elements.filterNotNull()
    } else {
        closure()
    }
}

inline fun <T: Any> ifLet(vararg elements: T?, closure: (List<T>) -> Unit) {
    if (elements.all { it != null }) {
        closure(elements.filterNotNull())
    }
}

용법:


// Will print
val (first, second, third) = guardLet("Hello", 3, Thing("Hello")) { return }
println(first)
println(second)
println(third)

// Will return
val (first, second, third) = guardLet("Hello", null, Thing("Hello")) { return }
println(first)
println(second)
println(third)

// Will print
ifLet("Hello", "A", 9) {
 (first, second, third) ->
 println(first)
 println(second)
 println(third)
}

// Won't print
ifLet("Hello", 9, null) {
 (first, second, third) ->
 println(first)
 println(second)
 println(third)
}

이것은 매우 좋지만 두 번째 입력에서 첫 번째 입력을 사용할 수있는 경우가 아직 없습니다. 예 : ifLet ( "A", toLower (first)) {// first = "A", second = "a"}
Otziii

ifLet 문에서 첫 번째 인수가 아직 풀리지 않았으므로 귀하와 같은 함수는 불가능합니다. guardLet 사용을 제안 할 수 있습니까? 매우 간단합니다. val (first) = guardLet (100) {return} val (second) = guardLet (101) {return} val average = average (first, second) 요청하신 내용이 아니라는 것을 알고 있지만 도움이되기를 바랍니다.
Dario Pellegrini

감사. 나는 이것을 해결하는 여러 가지 방법이 있습니다. 그 이유는 Swift에서 쉼표로 구분 된 여러 ifLets를 가질 수 있으며 이전 검사의 변수를 사용할 수 있기 때문입니다. Kotlin에서도 가능했으면합니다. :)
Otziii

1
수락 된 응답 일 수 있지만 모든 호출에 오버 헤드가 있습니다. vm은 먼저 Function 객체를 생성하기 때문입니다. 또한 dex 제한을 고려하면 모든 고유 검사에 대해 2 개의 메서드 참조가있는 Function 클래스 선언이 추가됩니다.
Oleksandr Albul

146

다음은 사용하려는 스타일, 모든 유형이 같거나 다른 경우, 목록에 알 수없는 항목 수에 따라 몇 가지 변형이 있습니다.

혼합 유형, 새 값을 계산하려면 모두 null이 아니어야합니다.

혼합 유형의 경우 어리석게 보일 수 있지만 혼합 유형에 대해 잘 작동하는 각 매개 변수 수에 대해 일련의 함수를 빌드 할 수 있습니다.

inline fun <T1: Any, T2: Any, R: Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? {
    return if (p1 != null && p2 != null) block(p1, p2) else null
}
inline fun <T1: Any, T2: Any, T3: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, block: (T1, T2, T3)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null) block(p1, p2, p3) else null
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, block: (T1, T2, T3, T4)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null && p4 != null) block(p1, p2, p3, p4) else null
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, T5: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, p5: T5?, block: (T1, T2, T3, T4, T5)->R?): R? {
    return if (p1 != null && p2 != null && p3 != null && p4 != null && p5 != null) block(p1, p2, p3, p4, p5) else null
}
// ...keep going up to the parameter count you care about

사용 예 :

val risk = safeLet(person.name, person.age) { name, age ->
  // do something
}   

목록에 null 항목이 없을 때 코드 블록 실행

여기서 두 가지 특징은 목록에 null이 아닌 항목이 모두있을 때 코드 블록을 실행하는 것이고, 두 번째는 목록에 null이 아닌 항목이 하나 이상있을 때 동일한 작업을 수행하는 것입니다. 두 경우 모두 null이 아닌 항목 목록을 코드 블록에 전달합니다.

기능 :

fun <T: Any, R: Any> Collection<T?>.whenAllNotNull(block: (List<T>)->R) {
    if (this.all { it != null }) {
        block(this.filterNotNull()) // or do unsafe cast to non null collectino
    }
}

fun <T: Any, R: Any> Collection<T?>.whenAnyNotNull(block: (List<T>)->R) {
    if (this.any { it != null }) {
        block(this.filterNotNull())
    }
}

사용 예 :

listOf("something", "else", "matters").whenAllNotNull {
    println(it.joinToString(" "))
} // output "something else matters"

listOf("something", null, "matters").whenAllNotNull {
    println(it.joinToString(" "))
} // no output

listOf("something", null, "matters").whenAnyNotNull {
    println(it.joinToString(" "))
} // output "something matters"

함수가 항목 목록을 받고 동일한 작업을 수행하도록 약간 변경했습니다.

fun <T: Any, R: Any> whenAllNotNull(vararg options: T?, block: (List<T>)->R) {
    if (options.all { it != null }) {
        block(options.filterNotNull()) // or do unsafe cast to non null collection
    }
}

fun <T: Any, R: Any> whenAnyNotNull(vararg options: T?, block: (List<T>)->R) {
    if (options.any { it != null }) {
        block(options.filterNotNull())
    }
}

사용 예 :

whenAllNotNull("something", "else", "matters") {
    println(it.joinToString(" "))
} // output "something else matters"

이러한 변형은 다음과 같은 반환 값을 갖도록 변경할 수 있습니다. let() .

null이 아닌 첫 번째 항목 사용 (Coalesce)

SQL Coalesce 함수와 유사하게 null이 아닌 첫 번째 항목을 반환합니다. 기능의 두 가지 특징 :

fun <T: Any> coalesce(vararg options: T?): T? = options.firstOrNull { it != null }
fun <T: Any> Collection<T?>.coalesce(): T? = this.firstOrNull { it != null }

사용 예 :

coalesce(null, "something", null, "matters")?.let {
    it.length
} // result is 9, length of "something"

listOf(null, "something", null, "matters").coalesce()?.let {
    it.length
}  // result is 9, length of "something"

기타 변형

... 다른 변형이 있지만 사양이 더 많으면 범위를 좁힐 수 있습니다.


1
다음 whenAllNotNull과 같이 디스트 럭처링 과 결합 할 수도 있습니다 listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f")..
dumptruckman

10

이를 위해 자신의 함수를 작성할 수 있습니다.

 fun <T, U, R> Pair<T?, U?>.biLet(body: (T, U) -> R): R? {
     val first = first
     val second = second
     if (first != null && second != null) {
         return body(first, second)
     }
     return null
 }

 (first to second).biLet { first, second -> 
      // body
 }

7

arrayIfNoNulls함수 를 만들 수 있습니다 .

fun <T : Any> arrayIfNoNulls(vararg elements: T?): Array<T>? {
    if (null in elements) {
        return null
    }
    @Suppress("UNCHECKED_CAST")
    return elements as Array<T>
}

그런 다음 다음을 사용하여 가변 개수의 값에 사용할 수 있습니다 let.

fun example(first: String?, second: String?) {
    arrayIfNoNulls(first, second)?.let { (first, second) ->
        // Do something if each element is not null
    }
}

이미 배열이있는 경우 takeIfNoNulls함수를 만들 수 있습니다 ( takeIf및에서 영감을 얻음 requireNoNulls) :

fun <T : Any> Array<T?>.takeIfNoNulls(): Array<T>? {
    if (null in this) {
        return null
    }
    @Suppress("UNCHECKED_CAST")
    return this as Array<T>
}

예:

array?.takeIfNoNulls()?.let { (first, second) ->
    // Do something if each element is not null
}

3

두 값만 확인하고 목록 작업을 수행 할 필요가없는 경우 :

fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
    if (value1 != null && value2 != null) {
        bothNotNull(value1, value2)
    }
}

사용 예 :

var firstString: String?
var secondString: String?
ifNotNull(firstString, secondString) { first, second -> Log.d(TAG, "$first, $second") }

2

사실, 간단하게 할 수 있습니다. ;)

if (first != null && second != null) {
    // your logic here...
}

Kotlin에서 정상적인 null 검사를 사용하는 데 아무런 문제가 없습니다.

또한 코드를 살펴 보는 모든 사람이 훨씬 더 읽기 쉽습니다.


36
가변 클래스 멤버를 다룰 ​​때는 충분하지 않습니다.
Michał K

3
대답은 이런 종류의를 제공 할 필요는 질문의 의도는 언어가 제공하기 때문에,이 처리의보다 "생산적인 방법을"찾을 수없는 let이러한 검사를 할 수있는 바로 가기
알레한드로 모야

1
유지 관리 측면에서 이것은 우아하지 않더라도 내 선택입니다. 이것은 모든 사람이 항상 실행하는 문제이며 언어가 처리해야합니다.
Brill Pappin

2

실제로 다음 도우미 함수를 사용하여 해결하는 것을 선호합니다.

fun <A, B> T(tuple: Pair<A?, B?>): Pair<A, B>? =
    if(tuple.first == null || tuple.second == null) null
    else Pair(tuple.first!!, tuple.second!!)

fun <A, B, C> T(tuple: Triple<A?, B?, C?>): Triple<A, B, C>? =
    if(tuple.first == null || tuple.second == null || tuple.third == null) null
    else Triple(tuple.first!!, tuple.second!!, tuple.third!!)


fun <A, B> T(first: A?, second: B?): Pair<A, B>? =
    if(first == null || second == null) null
    else Pair(first, second)

fun <A, B, C> T(first: A?, second: B?, third: C?): Triple<A, B, C>? =
        if(first == null || second == null || third == null) null
        else Triple(first, second, third)

사용 방법은 다음과 같습니다.

val a: A? = someValue
val b: B? = someOtherValue
T(a, b)?.let { (a, b) ->
  // Shadowed a and b are of type a: A and b: B
  val c: C? = anotherValue
  T(a, b, c)
}?.let { (a, b, c) ->
  // Shadowed a, b and c are of type a: A, b: B and c: C
  .
  .
  .
}

1

이 문제는 with의 동작을 다소 복제하지만 여러 매개 변수를 사용하고 모든 매개 변수의 함수 만 null이 아닌 일부 함수를 만들어 해결했습니다.

fun <R, A, B> withNoNulls(p1: A?, p2: B?, function: (p1: A, p2: B) -> R): R? = p1?.let { p2?.let { function.invoke(p1, p2) } }
fun <R, A, B, C> withNoNulls(p1: A?, p2: B?, p3: C?, function: (p1: A, p2: B, p3: C) -> R): R? = p1?.let { p2?.let { p3?.let { function.invoke(p1, p2, p3) } } }
fun <R, A, B, C, D> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, function: (p1: A, p2: B, p3: C, p4: D) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { function.invoke(p1, p2, p3, p4) } } } }
fun <R, A, B, C, D, E> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, p5: E?, function: (p1: A, p2: B, p3: C, p4: D, p5: E) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { p5?.let { function.invoke(p1, p2, p3, p4, p5) } } } } }

그런 다음 다음과 같이 사용합니다.

withNoNulls("hello", "world", Throwable("error")) { p1, p2, p3 ->
    p3.printStackTrace()
    p1.plus(" ").plus(p2)
}?.let {
    Log.d("TAG", it)
} ?: throw Exception("One or more parameters was null")

이것의 명백한 문제는 필요한 각 경우 (변수 수)에 대해 함수를 정의해야한다는 것입니다. 그러나 적어도 코드를 사용할 때 코드가 깨끗해 보인다고 생각합니다.


1

당신은 또한 이것을 할 수 있습니다

if (listOfNotNull(var1, var2, var3).size == 3) {
        // All variables are non-null
}

컴파일러는 vars가 null이 아니라는 것을 보장 할 수 없다고 여전히 불평합니다
Peter Graham

1

예상 답변을 약간 업그레이드했습니다.

inline fun <T: Any, R: Any> ifLet(vararg elements: T?, closure: (List<T>) -> R): R? {
    return if (elements.all { it != null }) {
        closure(elements.filterNotNull())
    } else null
}

이것은 이것을 가능하게합니다 :

iflet("first", "sconed") {
    // do somehing
} ?: run {
    // do this if one of the params are null
}

멋지지만 매개 변수는 이름이 지정되지 않았으며 유형을 공유해야합니다.
Daniel Gomez Rico

0

값의 양을 확인하려면 다음을 사용할 수 있습니다.

    fun checkNulls(vararg elements: Any?, block: (Array<*>) -> Unit) {
        elements.forEach { if (it == null) return }
        block(elements.requireNoNulls())
    }

그리고 다음과 같이 사용됩니다.

    val dada: String? = null
    val dede = "1"

    checkNulls(dada, dede) { strings ->

    }

블록에 전송 된 요소는 와일드 카드를 사용하고 있습니다. 값에 액세스하려면 유형을 확인해야합니다. 하나의 유형 만 사용해야하는 경우이를 제네릭으로 변경할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.