Подтвердить что ты не робот

Множественная переменная let в Kotlin

Есть ли способ связать несколько разрешений для нескольких переменных с нулевым значением в kotlin?

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
    }
}
4b9b3361

Ответ 1

Если интересно, вот две мои функции для решения этой проблемы.

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)
}

Ответ 2

Вот несколько вариантов, в зависимости от того, какой стиль вы хотите использовать, если у вас есть все одинакового или разных типов, и если список неизвестного количества элементов...

Смешанные типы, все не должны быть нулевыми, чтобы вычислить новое значение

Для смешанных типов вы можете построить ряд функций для каждого счетчика параметров, которые могут выглядеть глупо, но хорошо работают для смешанных типов:

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
}
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
}
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
}
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
}   

Выполнить блок кода, когда в списке нет пустых элементов

Здесь два варианта: сначала выполнить блок кода, когда список содержит все ненулевые элементы, а затем выполнить то же самое, если в списке есть хотя бы один ненулевой элемент. Оба случая передают список ненулевых элементов в блок кода:

Функции:

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().

Используйте первый ненулевой элемент (Coalesce)

Подобно функции SQL Coalesce, вернуть первый ненулевой элемент. Два вида функции:

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"

Другие варианты

... Существуют и другие варианты, но с большей спецификацией это можно сузить.

Ответ 3

Вы можете написать свою собственную функцию для этого:

 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
 }

Ответ 4

Вы можете создать функцию 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
}

Ответ 5

В случае просто проверки двух значений, а также отсутствия работы со списками:

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") }

Ответ 6

Собственно, вы можете просто сделать это, понимаете?;)

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

Нет ничего плохого в том, что вы используете нормальную нуль-проверку в Kotlin.

И это более читаемо для всех, кто будет смотреть ваш код.

Ответ 7

Я на самом деле предпочитаю решать его, используя следующие вспомогательные функции:

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
  .
  .
  .
}

Ответ 8

Для любого количества проверяемых значений вы можете использовать это:

    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 ->

    }

элементы, отправляемые в блок, используют подстановочный знак, вам нужно проверить типы, если вы хотите получить доступ к значениям, если вам нужно использовать только один тип, вы можете преобразовать его в обобщенные

Ответ 9

На момент написания этой статьи в Kotlin 1.3 не было встроенного механизма для поддержки нескольких цепочек, позволяющих использовать один вкладыш.

Язык Swift поддерживает множественные и цепочки позволяет очень элегантно (на мой взгляд) через

if let a = a, let b = b, let c = a.doSomething(b) {
  print(c)
} else {
  print("something was null")
}

Это огорчает меня всякий раз, когда мне нужно сделать что-то подобное в Kotlin, потому что код оказывается многословным и немного уродливым.

Надеюсь, что-нибудь похожее мы получим в Котлине на днях!