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.