Várias verificações de nulos em Java 8

99

Eu tenho o código abaixo, que é um pouco feio para várias verificações de nulos.

String s = null;

if (str1 != null) {
    s = str1;
} else if (str2 != null) {
    s = str2;
} else if (str3 != null) {
    s = str3;
} else {
    s = str4;
}

Então tentei usar Optional.ofNullablecomo abaixo, mas ainda é difícil de entender se alguém lê meu código. qual é a melhor abordagem para fazer isso no Java 8.

String s = Optional.ofNullable(str1)
                   .orElse(Optional.ofNullable(str2)
                                   .orElse(Optional.ofNullable(str3)
                                                   .orElse(str4)));

No Java 9, podemos usar Optional.ofNullablecom OR, mas no Java8 existe alguma outra abordagem?

faiscador
fonte
4
A orsintaxe Java9 String s = Optional.ofNullable(str1) .or(() -> Optional.ofNullable(str2)) .or(() -> Optional.ofNullable(str3)) .orElse(str4);não parece tão boa quanto a que Stream.ofeu faria.
Naman de
2
@ OleV.V. Nada de errado, o OP já está ciente disso e está buscando algo específico para o Java-8.
Naman de
3
Eu sei que o usuário está pedindo uma solução específica do Java-8, mas, em uma observação geral, eu iria comStringUtils.firstNonBlank()
Mohamed Anees A
3
O problema é que Java 8 / streams não é a melhor solução para isso. Esse código realmente cheira a um refatorador, mas sem mais contexto é muito difícil dizer. Para começar - por que três objetos provavelmente tão intimamente relacionados ainda não estão em uma coleção?
Bill K de
2
@MohamedAneesA teria fornecido a melhor resposta (como um comentário), mas não especificou a fonte de StringUtils neste caso. De qualquer forma, se você TEM que tê-los como um monte de strings separadas, codificá-los como um método vargs como "firstNonBlank" é o ideal, a sintaxe interna será uma matriz criando um loop for-each simples com um retorno ao encontrar um valor não nulo trivial e óbvio. Nesse caso, fluxos de java 8 são um incômodo atraente. eles tentam você a embutir e complicar algo que deveria ser um método / loop simples.
Bill K de

Respostas:

172

Você pode fazer assim:

String s = Stream.of(str1, str2, str3)
    .filter(Objects::nonNull)
    .findFirst()
    .orElse(str4);
Ravindra Ranwala
fonte
16
Este. Pense no que você precisa, não no que você tem.
Thorbjørn Ravn Andersen
21
Qual é a sobrecarga de velocidade? Criar objetos Stream, chamar 4 métodos, criar arrays temporários ( {str1, str2, str3}) parece muito mais lento do que local ifou ?:, que o tempo de execução Java pode otimizar. Existe alguma otimização específica do Stream em javace o Java runtime que o torna tão rápido quanto ?:? Do contrário, não recomendarei esta solução em código de desempenho crítico.
pontos de
16
@pts é muito provável que seja mais lento do que o ?:código e tenho certeza que você deve evitá-lo em código de desempenho crítico. No entanto, é muito mais legível e você deve recomendá-lo em código IMO de desempenho não crítico, que tenho certeza que compõe mais de 99% do código.
Aaron de
7
@pts Não há otimizações específicas do Stream, nem em javacnem no tempo de execução. Isso não impede as otimizações gerais, como embutir todo o código, seguido pela eliminação de operações redundantes. Em princípio, o resultado final poderia ser tão eficiente quanto as expressões condicionais simples; no entanto, é bastante improvável que chegue lá, pois o tempo de execução gastaria o esforço necessário apenas nos caminhos de código mais ativos.
Holger
22
Ótima solução! Para aumentar um pouco a legibilidade, sugiro adicionar str4aos parâmetros de Stream.of(...)e usar orElse(null)no final.
danielp de
73

E quanto ao operador condicional ternário?

String s = 
    str1 != null ? str1 : 
    str2 != null ? str2 : 
    str3 != null ? str3 : str4
;
Eran
fonte
50
na verdade, ele está procurando "a melhor abordagem para fazer isso em Java8". Essa abordagem pode ser usada em Java8, portanto, tudo depende apenas do que o OP entende por "o melhor" e em que base ele baseia a decisão sobre o que é melhor.
Stultuske
17
Eu geralmente não gosto de ternários aninhados, mas isso parece bem limpo.
JollyJoker de
11
É uma grande dor de analisar para quem não está lendo operadores ternários aninhados todos os dias.
Cubic de
2
@Cubic: Se você acha que é difícil de analisar, escreva um comentário como // take first non-null of str1..3. Depois de saber o que faz, fica fácil ver como.
Peter Cordes
4
@Cubic No entanto, este é um padrão repetido simples . Depois de analisar a linha 2, você analisou a coisa toda, não importando quantos casos foram incluídos. E quando terminar, você aprendeu a expressar uma seleção semelhante de dez casos diferentes de uma maneira breve e relativamente simples. Na próxima vez que você vir uma ?:escada, saberá o que ela faz. (Aliás, os programadores funcionais conhecem isso como (cond ...)cláusulas: é sempre um guarda seguido pelo valor apropriado a ser usado quando o guarda é verdadeiro.)
cmaster - restabelecer monica
35

Você também pode usar um loop:

String[] strings = {str1, str2, str3, str4};
for(String str : strings) {
    s = str;
    if(s != null) break;
}
ernest_k
fonte
27

As respostas atuais são boas, mas você realmente deve colocar isso em um método utilitário:

public static Optional<String> firstNonNull(String... strings) {
    return Arrays.stream(strings)
            .filter(Objects::nonNull)
            .findFirst();
}

Esse método está em minha Utilaula há anos, torna o código muito mais limpo:

String s = firstNonNull(str1, str2, str3).orElse(str4);

Você pode até torná-lo genérico:

@SafeVarargs
public static <T> Optional<T> firstNonNull(T... objects) {
    return Arrays.stream(objects)
            .filter(Objects::nonNull)
            .findFirst();
}

// Use
Student student = firstNonNull(student1, student2, student3).orElseGet(Student::new);
walen
fonte
10
FWIW, no SQL, essa função é chamada de coalesce , então eu também chamo isso no meu código. Se isso funciona para você, depende do quanto você gosta de SQL, na verdade.
Tom Anderson
5
Se você for colocá-lo em um método utilitário, também pode torná-lo eficiente.
Todd Sewell de
1
@ToddSewell, o que você quer dizer?
Gustavo Silva
5
@GustavoSilva Todd provavelmente quer dizer que, sendo um método utilitário meu, não adianta usar Arrays.stream()quando posso fazer o mesmo com a fore a != null, que é mais eficiente. E Todd estaria certo. No entanto, quando codifiquei esse método, estava procurando uma maneira de fazer isso usando recursos do Java 8, assim como o OP, então é isso.
walen
4
@GustavoSilva Sim, basicamente é isso: se você vai colocar isso como uma função util, as preocupações com o código limpo não são mais tão importantes, então você pode usar uma versão mais rápida.
Todd Sewell
13

Eu uso uma função auxiliar, algo como

T firstNonNull<T>(T v0, T... vs) {
  if(v0 != null)
    return v0;
  for(T x : vs) {
    if (x != null) 
      return x;
  }
  return null;
}

Então, este tipo de código pode ser escrito como

String s = firstNonNull(str1, str2, str3, str4);
Michael Anderson
fonte
1
Por que o v0parâmetro extra ?
tobias_k
1
@tobias_k O parâmetro extra ao usar varargs é a maneira idiomática em Java de exigir 1 ou mais argumentos em vez de 0 ou mais. (Veja o item 53 de Effective Java, Ed. 3., que usa mincomo exemplo.) Estou menos convencido de que isso seja apropriado aqui.
Nick
3
@Nick Sim, eu adivinhei isso, mas a função funcionaria tão bem (na verdade, se comportaria exatamente da mesma) sem ela.
tobias_k
1
Um dos principais motivos para passar um primeiro argumento extra é ser explícito sobre o comportamento ao passar um array. Nesse caso, desejo firstNonNull(arr)retornar arr se não for nulo. Se fosse, firstNonNull(T... vs)ele retornaria a primeira entrada não nula em arr.
Michael Anderson
4

Uma solução que pode ser aplicada a quantos elementos você quiser pode ser:

Stream.of(str1, str2, str3, str4)
      .filter(Object::nonNull)
      .findFirst()
      .orElseThrow(IllegalArgumentException::new)

Você pode imaginar uma solução como a abaixo, mas a primeira garante non nullitypara todos os elementos

Stream.of(str1, str2, str3).....orElse(str4)
azro
fonte
6
orElse, str4apesar de ser nulo, na verdade
Naman
1
@nullpointer Ou orElseNull, que tem o mesmo valor se str4for nulo.
tobias_k
3

Você também pode agrupar todas as Strings em um array de String e fazer um loop for para verificar e interromper o loop depois de atribuído. Supondo que s1, s2, s3, s4 sejam todos Strings.

String[] arrayOfStrings = {s1, s2, s3};


s = s4;

for (String value : arrayOfStrings) {
    if (value != null) { 
        s = value;
        break;
    }
}

Editado para lançar a condição padrão para s4 se nenhum for atribuído.

danielctw
fonte
Você omitiu s4(ou str4), que deve ser atribuído sno final, mesmo que seja nulo.
displayName
3

Método baseado e simples.

String getNonNull(String def, String ...strings) {
    for(int i=0; i<strings.length; i++)
        if(strings[i] != null)
             return s[i];
    return def;
}

E use-o como:

String s = getNonNull(str4, str1, str2, str3);

É simples de fazer com arrays e é bonito.

Madhusoodan P
fonte
0

Se você usar o Apache Commons Lang 3, ele pode ser escrito assim:

String s = ObjectUtils.firstNonNull(str1, str2, str3, str4);

O uso de ObjectUtils.firstNonNull(T...)foi retirado desta resposta . Diferentes abordagens também foram apresentadas em questões relacionadas .

lczapski
fonte