Como obter o primeiro valor não nulo em Java?

154

Existe um equivalente em Java da COALESCEfunção do SQL ? Ou seja, existe alguma maneira de retornar o primeiro valor não nulo de várias variáveis?

por exemplo

Double a = null;
Double b = 4.4;
Double c = null;

Eu quero de alguma forma tem uma declaração de que irá retornar o primeiro valor não nulo de a, be c- neste caso, ele voltaria b, ou 4.4. (Algo como o método sql - return COALESCE(a,b,c)). Eu sei que posso fazê-lo explicitamente com algo como:

return a != null ? a : (b != null ? b : c)

Mas me perguntei se havia alguma função incorporada e aceita para fazer isso.

froadie
fonte
3
Você não deve precisar de uma função como essa, pois não calcularia 'c' se 'b' tiver a resposta que deseja. ou seja, você não criaria uma lista de respostas possíveis apenas para manter uma.
Peter Lawrey
Advertência: Nem todo o RDBMS curto-circuito no COALESCE. A Oracle começou recentemente a fazê-lo.
30512 Adam Adam Gent
3
@ BrainSlugs83 Sério? Java deveria?
Dmitry Ginzburg

Respostas:

108

Não, não existe.

O mais próximo que você pode chegar é:

public static <T> T coalesce(T ...items) {
    for(T i : items) if(i != null) return i;
    return null;
}

Por razões eficientes, você pode lidar com os casos comuns da seguinte maneira:

public static <T> T coalesce(T a, T b) {
    return a == null ? b : a;
}
public static <T> T coalesce(T a, T b, T c) {
    return a != null ? a : (b != null ? b : c);
}
public static <T> T coalesce(T a, T b, T c, T d) {
    return ...
}
les2
fonte
3
os motivos de eficiência mencionados acima são que uma alocação de matriz ocorrerá sempre que você invocar a versão var arg do método. isso pode ser um desperdício para a mão cheia de itens, que eu suspeito que será de uso comum.
Les2
Legal. Obrigado. Nesse caso, eu provavelmente vou ficar com os operadores condicionais aninhados neste caso, uma vez que é a única vez que tem de ser utilizado eo método definido pelo usuário seria um exagero ...
froadie
8
Eu ainda o colocaria em um método auxiliar particular, em vez de deixar um bloco condicional de "aparência assustadora" no código - "o que isso faz?" Dessa forma, se você precisar usá-lo novamente, poderá usar as ferramentas de refatoração no seu IDE para mover o método para a classe de utilitário. ter o método nomeado ajuda a documentar a intenção do código, o que é sempre bom, IMO. (e a sobrecarga da versão não var-args provavelmente não é mensurável.)
les2
10
Cuidado: In coalesce(a, b), se bé uma expressão complexa e anão é null, bainda é avaliado. Este não é o caso do operador condicional?:. Veja esta resposta .
Pang
isso exige que todos os argumentos sejam pré-calculados antes da chamada para coalescer, sem sentido por razões de desempenho
Ivan G.
59

Se houver apenas duas variáveis ​​para verificar e você estiver usando o Guava, poderá usar MoreObjects.firstNonNull (T first, T second) .

Dave
fonte
49
Objects.firstNonNull leva apenas dois argumentos; não há varargs equivalentes na goiaba. Além disso, ele lança uma NullPointerException se os dois argumentos forem nulos - isso pode ou não ser desejável.
2
Bom comentário, Jake. Essa NullPointerException geralmente restringe o uso de Objects.firstNonNull. No entanto, é a abordagem do Guava para evitar nulos.
Anton Shchastnyi
4
Agora, esse método foi descontinuado e a alternativa recomendada é MoreObjects.firstNonNull
davidwebster48
1
Se NPE é indesejada, e depois ver esta resposta
OrangeDog
51

Se houver apenas duas referências para testar e você estiver usando o Java 8, poderá usar

Object o = null;
Object p = "p";
Object r = Optional.ofNullable( o ).orElse( p );
System.out.println( r );   // p

Se você importar estático opcional, a expressão não será muito ruim.

Infelizmente, seu caso com "várias variáveis" não é possível com um método opcional. Em vez disso, você pode usar:

Object o = null;
Object p = null;
Object q = "p";

Optional<Object> r = Stream.of( o, p, q ).filter( Objects::nonNull ).findFirst();
System.out.println( r.orElse(null) );   // p
Christian Ullenboom
fonte
23

Seguindo a resposta do LES2, você pode eliminar algumas repetições na versão eficiente, chamando a função sobrecarregada:

public static <T> T coalesce(T a, T b) {
    return a != null ? a : b;
}
public static <T> T coalesce(T a, T b, T c) {
    return a != null ? a : coalesce(b,c);
}
public static <T> T coalesce(T a, T b, T c, T d) {
    return a != null ? a : coalesce(b,c,d);
}
public static <T> T coalesce(T a, T b, T c, T d, T e) {
    return a != null ? a : coalesce(b,c,d,e);
}
Eric
fonte
5
+1 para bonita. Não tenho certeza sobre os benefícios de eficiência no loop simples, mas se você quiser obter uma eficiência minúscula dessa maneira, pode ser bonito.
Carl Manaster
3
dessa forma, torna-se muito menos doloroso e menos propenso a erros escrever as variantes sobrecarregadas!
Les2
2
O objetivo da versão eficiente era não desperdiçar memória alocando uma matriz usando varargs. Aqui, você está desperdiçando memória criando um quadro de pilha para cada coalesce()chamada aninhada . A chamada coalesce(a, b, c, d, e)cria até 3 quadros de pilha para calcular.
Luke
10

Essa situação exige algum pré-processador. Como se você escrever uma função (método estático) que seleciona o primeiro valor não nulo, ela avalia todos os itens. É problemático se alguns itens forem chamadas de método (podem ser chamadas de método caras). E esses métodos são chamados mesmo se algum item antes deles não for nulo.

Algumas funcionam assim

public static <T> T coalesce(T ...items) 

deve ser usado, mas antes de compilar no código de bytes, deve haver um pré-processador que encontre usos dessa "função de coalescência" e a substitua por construções como

a != null ? a : (b != null ? b : c)

Atualização 2014-09-02:

Graças ao Java 8 e Lambdas, é possível ter uma verdadeira coalescência em Java! Incluindo o recurso crucial: expressões específicas são avaliadas apenas quando necessário - se uma anterior não for nula, as seguintes não serão avaliadas (métodos não são chamados, computação ou operações de disco / rede não são realizadas).

Eu escrevi um artigo sobre o Java 8: coalescer - hledáme neNULLové hodnoty - (escrito em tcheco, mas espero que os exemplos de código sejam compreensíveis para todos).

Franta
fonte
1
Bom artigo - seria bom tê-lo em inglês, no entanto.
Quantum
1
Há algo nessa página do blog que não funciona com o Google Tradutor. :-(
HairOfTheDog 14/05
5

Com o Goiaba, você pode fazer:

Optional.fromNullable(a).or(b);

que não lança NPE se ambos ae bsão null.

Edição: Eu estava errado, ele lança NPE. A maneira correta, como comentado por Michal Čizmazia, é:

Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull();
Jamol
fonte
1
Ei, sim:java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
Michal Čizmazia 28/08
1
Isso faz o truque:Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull()
Michal Čizmazia 28/08
4

Apenas para completar, o caso "várias variáveis" é realmente possível, embora nem um pouco elegante. Por exemplo, para as variáveis o, pe q:

Optional.ofNullable( o ).orElseGet(()-> Optional.ofNullable( p ).orElseGet(()-> q ) )

Por favor, note o uso de orElseGet()assistir ao caso que o, peq não são variáveis, mas expressões caro ou com efeitos colaterais indesejáveis.

No caso mais geral coalesce(e[1],e[2],e[3],...,e[N])

coalesce-expression(i) ==  e[i]  when i = N
coalesce-expression(i) ==  Optional.ofNullable( e[i] ).orElseGet(()-> coalesce-expression(i+1) )  when i < N

Isso pode gerar expressões excessivamente longas. No entanto, se estamos tentando mudar para um mundo sem null, v[i]provavelmente já somos do tipo Optional<String>, e não simplesmente String. Nesse caso,

result= o.orElse(p.orElse(q.get())) ;

ou no caso de expressões:

result= o.orElseGet(()-> p.orElseGet(()-> q.get() ) ) ;

Além disso, se você também está se movendo para um estilo funcional-declarativa, o, p, e qdeve ser do tipo Supplier<String>como em:

Supplier<String> q= ()-> q-expr ;
Supplier<String> p= ()-> Optional.ofNullable(p-expr).orElseGet( q ) ;
Supplier<String> o= ()-> Optional.ofNullable(o-expr).orElseGet( p ) ;

E então o todo se coalescereduz simplesmente a o.get().

Para um exemplo mais concreto:

Supplier<Integer> hardcodedDefaultAge= ()-> 99 ;
Supplier<Integer> defaultAge= ()-> defaultAgeFromDatabase().orElseGet( hardcodedDefaultAge ) ;
Supplier<Integer> ageInStore= ()-> ageFromDatabase(memberId).orElseGet( defaultAge ) ;
Supplier<Integer> effectiveAge= ()-> ageFromInput().orElseGet( ageInStore ) ;

defaultAgeFromDatabase(), ageFromDatabase()E ageFromInput()já ia voltar Optional<Integer>, naturalmente.

E então o coalescetorna - se effectiveAge.get()ou simplesmente effectiveAgese estamos felizes com a Supplier<Integer>.

IMHO, com o Java 8, veremos cada vez mais código estruturado dessa maneira, pois é extremamente auto-explicativo e eficiente ao mesmo tempo, especialmente em casos mais complexos.

Sinto falta de uma classe Lazy<T>que invoca Supplier<T>apenas uma vez, mas preguiçosamente, além da consistência na definição de Optional<T>(ie Optional<T>- Optional<T>operadores, ou mesmo Supplier<Optional<T>>).

Mario Rossi
fonte
4

Você pode tentar isso:

public static <T> T coalesce(T... t) {
    return Stream.of(t).filter(Objects::nonNull).findFirst().orElse(null);
}

Com base nesta resposta

Lucas León
fonte
3

Que tal usar fornecedores quando você deseja evitar avaliar algum método caro?

Como isso:

public static <T> T coalesce(Supplier<T>... items) {
for (Supplier<T> item : items) {
    T value = item.get();
    if (value != null) {
        return value;
    }
    return null;
}

E depois usá-lo assim:

Double amount = coalesce(order::firstAmount, order::secondAmount, order::thirdAmount)

Você também pode usar métodos sobrecarregados para as chamadas com dois, três ou quatro argumentos.

Além disso, você também pode usar fluxos com algo como isto:

public static <T> T coalesce2(Supplier<T>... s) {
    return Arrays.stream(s).map(Supplier::get).filter(Objects::nonNull).findFirst().orElse(null);
}
Triqui
fonte
Por que colocar o primeiro argumento em um Supplierse ele será inspecionado de qualquer maneira? Por uma questão de uniformidade?
Inego 20/09/19
0

E se:

firstNonNull = FluentIterable.from(
    Lists.newArrayList( a, b, c, ... ) )
        .firstMatch( Predicates.notNull() )
            .or( someKnownNonNullDefault );

O Java ArrayList permite convenientemente entradas nulas e essa expressão é consistente, independentemente do número de objetos a serem considerados. (Nesta forma, todos os objetos considerados precisam ser do mesmo tipo.)

Lonnie
fonte
-3
Object coalesce(Object... objects)
{
    for(Object o : object)
        if(o != null)
            return o;
    return null;
}
Eric
fonte
2
Deus, eu odeio genéricos. Eu vi o que o seu significava logo de cara. Eu tive que olhar para o @ LES2 duas vezes para descobrir que ele estava fazendo a mesma coisa (e provavelmente "melhor")! +1 para maior clareza
Bill K
Sim, os genéricos são o caminho a percorrer. Mas não estou tão familiarizado com os meandros.
Eric
10
Hora de aprender genéricos :-). Há pouca diferença entre o exemplo de @ LES2 e este, além de T em vez de Objeto. -1 para criar uma função que forçará a conversão do valor retornado para Double. Também para nomear um método Java em letras maiúsculas, o que pode ser bom em SQL, mas não é bom estilo em Java.
Avi
1
Eu percebo que all-caps é uma prática ruim. Eu estava apenas mostrando ao OP como escrever uma função com o nome que eles solicitaram. Concordado, o elenco de volta Doubleestá longe de ser o ideal. Eu simplesmente não sabia que funções estáticas poderiam receber parâmetros de tipo. Eu pensei que eram apenas aulas.
Eric