Множественная переменная 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 } } 

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

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

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

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

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

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

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

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

В духе различных *NotNull и *OrNull от Kotlin вы можете создать функцию arrayOfNotNullOrNull которая позволит вам создать массив из «множественных переменных с null значением», если все переменные не являются null и null :

 fun <T : Any> arrayOfNotNullOrNull(vararg elements: T?): Array<T>? { for (element in elements) { if (element == null) { return null } } return elements as Array<T> } 

Затем вы можете использовать его для переменного количества значений с let :

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

Если у вас уже есть значения с noNullsOrNull значением в коллекции, вы можете создать noNullsOrNull расширения noNullsOrNull подобную kotlin.collections.requireNoNulls но которая возвращает null вместо исключения исключения.

На самом деле, вы можете просто сделать это, понимаете? 😉

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

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

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

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

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