Variável múltipla que entra Kotlin

127

Existe alguma maneira de encadear múltiplas permissões múltiplas variáveis ​​anuláveis ​​no kotlin?

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

Quero dizer, algo como isto:

fun example(first: String?, second: String?) {
    first?.let && second?.let { 
        // Do something just if both are != null
    }
}
Daniel Gomez Rico
fonte
1
Você quer N itens, não apenas 2? Todos os itens precisam do mesmo tipo ou de tipos diferentes? Todos os valores devem ser passados ​​para a função, como lista ou como parâmetros individuais? O valor de retorno deve ser um único item ou um grupo do mesmo número de itens que a entrada?
Jayson Minard
Eu preciso de todos os argumentos, pode haver dois para este caso, mas também queria saber uma maneira de fazer isso por mais, em rápida é tão fácil.
Daniel Gomez Rico
Você está procurando algo diferente das respostas abaixo, se sim, comente qual é a diferença que está procurando.
Jayson Minard
Como seria se referir ao primeiro "it" dentro do segundo bloco let?
Javier Mendonça

Respostas:

48

Se estiver interessado, aqui estão duas das minhas funções para resolver isso.

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

Uso:


// 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)
}
Dario Pellegrini
fonte
Isso é muito bom, mas ainda estou faltando um caso em que posso usar a primeira entrada na segunda. Exemplo: ifLet ("A", toLower (first)) {// first = "A", second = "a"}
Otziii 13/08/19
Como na instrução ifLet, o primeiro argumento ainda não foi desempacotado, uma função como a sua não é possível. Posso sugerir o uso do guardLet? É bem direto. val (primeiro) = guardLet (100) {retorno} val (segundo) = guardLet (101) {retorno} val média = média (primeiro, segundo) Eu sei que não é isso que você pediu, mas espero que ajude.
Dario Pellegrini
Obrigado. Eu tenho várias maneiras de resolver isso, a razão para dizer é que, no Swift, é possível ter vários ifLets um após o outro separados por vírgula e eles podem usar as variáveis ​​da verificação anterior. Eu gostaria que isso fosse possível em Kotlin também. :)
Oct
1
Pode ser uma resposta aceita, mas há sobrecarga em todas as chamadas. Porque vm cria o objeto Function em primeiro lugar. Também considerando a limitação dex, isso adicionará declaração de classe Function com 2 referências de método para todas as verificações exclusivas.
Oleksandr Albul 01/10/19
146

Aqui estão algumas variações, dependendo do estilo que você deseja usar, se você tiver tudo do mesmo ou de tipos diferentes e se o número desconhecido de itens da lista ...

Tipos mistos, nem todos devem ser nulos para calcular um novo valor

Para tipos mistos, você pode criar uma série de funções para cada contagem de parâmetros que podem parecer bobas, mas funcionam bem para tipos mistos:

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

Exemplo de uso:

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

Executar bloco de código quando a lista não tiver itens nulos

Dois tipos aqui: primeiro para executar o bloco de código quando uma lista tiver todos os itens não nulos; e o segundo para fazer o mesmo quando uma lista tiver pelo menos um item não nulo. Ambos os casos passam uma lista de itens não nulos para o bloco de código:

Funções:

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

Exemplo de uso:

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"

Uma pequena alteração para que a função receba a lista de itens e faça as mesmas operações:

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

Exemplo de uso:

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

Essas variações podem ser alteradas para ter valores de retorno como let().

Use o primeiro item não nulo (coalescência)

Semelhante a uma função SQL Coalesce, retorne o primeiro item não nulo. Dois sabores da função:

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

Exemplo de uso:

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"

Outras variações

... Existem outras variações, mas com mais especificações, isso pode ser reduzido.

Jayson Minard
fonte
1
Você também pode combinar whenAllNotNullcom desestruturando assim: listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f").
precisa
10

Você pode escrever sua própria função para isso:

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

Você pode criar uma arrayIfNoNullsfunção:

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

Você pode usá-lo para um número variável de valores com let:

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

Se você já possui uma matriz, pode criar uma takeIfNoNullsfunção (inspirada em takeIfe requireNoNulls):

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

Exemplo:

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

No caso de apenas verificar dois valores e também não precisar trabalhar com listas:

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

Exemplo de uso:

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

Na verdade, você pode simplesmente fazer isso, sabe? ;)

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

Não há nada errado em usar uma verificação nula normal no Kotlin.

E é muito mais legível para todos que analisam seu código.

Grzegorz D.
fonte
36
Não será suficiente ao lidar com um membro da classe mutável.
Michał K 07/07
3
Não há necessidade de dar esse tipo de resposta, a intenção da questão é encontrar uma "maneira produtiva" mais de lidar com isso, já que a linguagem fornece o letatalho para fazer essas verificações
Alejandro Moya
1
Em termos de manutenção, essa é a minha escolha, mesmo que não seja tão elegante. Esta é claramente uma questão que todos enfrentam o tempo todo, e o idioma deve lidar.
Brill Pappin 03/04
2

Na verdade, prefiro resolvê-lo usando as seguintes funções auxiliares:

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)

E aqui está como você deve usá-los:

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
  .
  .
  .
}
Moshe Bixenshpaner
fonte
1

Resolvi isso criando algumas funções que replicam mais ou menos o comportamento de with, mas usam vários parâmetros e chama apenas a função de todos os parâmetros que não são nulos.

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

Então eu uso assim:

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

O problema óbvio disso é que preciso definir uma função para cada caso (número de variáveis) de que preciso, mas pelo menos acho que o código parece limpo ao usá-las.

Jon
fonte
1

Você também pode fazer isso

if (listOfNotNull(var1, var2, var3).size == 3) {
        // All variables are non-null
}
Amy Eubanks
fonte
O compilador ainda reclamará que não pode garantir que os vars não sejam nulos
Peter Graham
1

Eu atualizei a resposta esperada um pouco:

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
}

isso torna isso possível:

iflet("first", "sconed") {
    // do somehing
} ?: run {
    // do this if one of the params are null
}
yohai knaani
fonte
Isso é legal, mas os parâmetros não são nomeados e devem compartilhar o tipo.
Daniel Gomez Rico
0

Para qualquer quantidade de valores a serem verificados, você pode usar o seguinte:

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

E será usado assim:

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

    checkNulls(dada, dede) { strings ->

    }

os elementos enviados ao bloco estão usando o curinga, você precisa verificar os tipos, se quiser acessar os valores, se precisar usar apenas um tipo, poderá alterar isso para genéricos

Alejandro Moya
fonte