Кілька змінних пускаємо в Котлін


127

Чи є спосіб зав'язати кілька літ для кількох змінних змінних в 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
    }
}

1
Вам потрібно N предметів, а не лише 2? Чи потрібні всі предмети одного типу або різних типів? Чи повинні всі значення передаватися у функції, як список, або як окремі параметри? Чи має значення повернення бути одним елементом або групою з такою ж кількістю елементів, що і вхідне?
Джейсон Мінард

Мені потрібні всі аргументи, їх може бути два для цього випадку, але також хотілося знати спосіб зробити це для більшого, у швидкому це так просто.
Даніель Гомес Ріко

Ви шукаєте щось інше, ніж відповіді нижче, якщо так, коментуйте, яка різниця ви шукаєте.
Джейсон Мінард

Як було б посилатися на перше "це" у другому дозволеному блоці?
Хав’єр Мендонча

Відповіді:


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 = "A", second = "a"}
Otziii

Тому що в операторі ifLet перший аргумент ще не розкручений, така функція, як ваша, неможлива. Чи можу я запропонувати використовувати guardLet? Це досить прямо вперед. val (перший) = guardLet (100) {return} val (другий) = guardLet (101) {return} val середній = середній (перший, другий) Я знаю, що це не те, що ви просили, але сподіваюся, що це допоможе.
Даріо Пеллегріні

Дякую. У мене є кілька способів вирішення цього питання, причина для того, щоб сказати, що в Swift можливо мати декілька ifLets після один одного, розділених комою, і вони можуть використовувати змінні попередньої перевірки. Я б хотів, щоб це було можливо і в Котліні. :)
Otziii

1
Можна було б прийняти відповідь, але на кожен дзвінок є накладні витрати. Тому що vm спочатку створює об’єкт функції. Також враховуючи обмеження dex, це додасть декларацію класу функцій з двома посиланнями методів для кожного унікального перевірки.
Олександр

146

Ось кілька варіантів, залежно від того, який стиль ви хочете використовувати, якщо у вас є все однакові або різні типи, і якщо в списку невідома кількість елементів ...

Змішані типи не повинні мати значення для обчислення нового значення

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

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
}   

Виконати блок коду, коли в списку немає нульових елементів

Тут два ароматизатори: спочатку виконати блок коду, коли у списку є всі ненулі елементи, а другий - зробити той самий, коли у списку є хоча б один ненульовий елемент. Обидва випадки передають список ненульових елементів до блоку коду:

Функції:

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"

Інші варіації

... Існують і інші варіанти, але, якщо більше специфікації, це може бути звужене.


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

Немає нічого поганого у використанні звичайної нульової перевірки в Котліні.

І це набагато читає для всіх, хто загляне у ваш код.


36
Цього буде недостатньо для роботи з членом класу, що змінюється.
Michał K

3
Не потрібно давати подібну відповідь, намір питання полягає у пошуку більш "продуктивного способу" вирішення цього питання, оскільки мова забезпечує letярлик для проведення цих перевірок
Алехандро Моя

1
Що стосується ремонту, це мій вибір, навіть якщо це не так елегантно. Це, очевидно, питання, з яким усі постійно стикаються, і мова повинна вирішуватися.
Брілл Паппін

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

Я вирішив це, створивши деякі функції, які більш-менш повторюють поведінку з, але приймає декілька параметрів і викликає лише функцію всіх параметрів, які не мають значення.

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
}

Компілятор все ще поскаржиться, що не може гарантувати, що пара не буде нульовим
Пітер Грем

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
}

Це здорово, але параметри не названі, і вони повинні поділяти тип.
Даніель Гомес Ріко

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

    }

елементи, надіслані до блоку, використовують підстановку, вам потрібно перевірити типи, якщо ви хочете отримати доступ до значень, якщо вам потрібно використовувати лише один тип, ви могли б вимкнути це в generics

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.