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.


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

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


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

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

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


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

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


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


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

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


-3
Object coalesce(Object... objects)
{
    for(Object o : object)
        if(o != null)
            return o;
    return null;
}

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.