Plusieurs vérifications nulles dans Java 8

99

J'ai le code ci-dessous qui est un peu moche pour plusieurs vérifications nulles.

String s = null;

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

J'ai donc essayé d'utiliser Optional.ofNullablecomme ci-dessous, mais c'est toujours difficile à comprendre si quelqu'un lit mon code. quelle est la meilleure approche pour le faire dans Java 8.

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

En Java 9, nous pouvons utiliser Optional.ofNullableavec OR, Mais en Java8, existe-t-il une autre approche?

étincelle
la source
4
La orsyntaxe Java9 String s = Optional.ofNullable(str1) .or(() -> Optional.ofNullable(str2)) .or(() -> Optional.ofNullable(str3)) .orElse(str4);ne semble pas aussi bonne que la Stream.ofsya je le ferais.
Naman
2
@ OleV.V. Rien de mal, l'OP en est déjà conscient et cherche quelque chose de spécifique à Java-8.
Naman
3
Je sais que l'utilisateur demande une solution spécifique à Java-8, mais d'une manière générale, j'irais avecStringUtils.firstNonBlank()
Mohamed Anees A
3
Le problème est que Java 8 / streams n'est pas la meilleure solution pour cela. Ce code sent vraiment comme un refactor est en ordre mais sans plus de contexte, c'est vraiment difficile à dire. Pour commencer - pourquoi trois objets probablement si étroitement liés ne sont-ils pas déjà dans une collection?
Bill K
2
@MohamedAneesA aurait fourni la meilleure réponse (en commentaire) mais n'a pas précisé la source de StringUtils dans ce cas. En tout cas, si vous DEVEZ les avoir sous forme de chaînes séparées, le coder comme une méthode vargs comme "firstNonBlank" est idéal, la syntaxe à l'intérieur sera un tableau faisant une simple boucle for-each avec un retour sur la recherche d'un valeur non nulle triviale et évidente. Dans ce cas, les flux java 8 sont une nuisance intéressante. ils vous incitent à intégrer et à compliquer quelque chose qui devrait être une simple méthode / boucle.
Bill K

Réponses:

173

Vous pouvez le faire comme ceci:

String s = Stream.of(str1, str2, str3)
    .filter(Objects::nonNull)
    .findFirst()
    .orElse(str4);
Ravindra Ranwala
la source
16
Ce. Pensez à ce dont vous avez besoin, pas à ce que vous avez.
Thorbjørn Ravn Andersen
21
Quelle est la surcharge de vitesse? La création d'objets Stream, l'appel de 4 méthodes, la création de tableaux temporaires ( {str1, str2, str3}) semblent beaucoup plus lents qu'un ifou local ?:, que le runtime Java peut optimiser. Existe-t-il une optimisation spécifique à Stream javacet le runtime Java qui le rendent aussi rapide que ?:? Sinon, je ne recommanderai pas cette solution dans un code critique pour les performances.
pts
16
@pts, il est très probable que cela soit plus lent que le ?:code et je suis sûr que vous devriez l'éviter dans le code critique pour les performances. Il est cependant beaucoup plus lisible et vous devriez le recommander dans un code IMO non critique pour les performances, qui, j'en suis sûr, fait plus de 99% du code.
Aaron
7
@pts Il n'y a pas d'optimisations spécifiques à Stream, ni dans javacni dans le runtime. Cela n'exclut pas les optimisations générales, comme l'inclusion de tout le code, suivies de l'élimination des opérations redondantes. En principe, le résultat final pourrait être aussi efficace que les expressions conditionnelles simples, cependant, il est plutôt improbable qu'il y parvienne un jour, car le runtime consacrerait l'effort nécessaire uniquement sur les chemins de code les plus chauds.
Holger
22
Excellente solution! Pour augmenter un peu la lisibilité, je suggérerais d'ajouter str4des paramètres Stream.of(...)et de l'utiliser orElse(null)à la fin.
danielp
73

Qu'en est-il de l'opérateur conditionnel ternaire?

String s = 
    str1 != null ? str1 : 
    str2 != null ? str2 : 
    str3 != null ? str3 : str4
;
Eran
la source
50
en fait, il recherche "la meilleure approche pour faire cela en Java8". Cette approche peut être utilisée dans Java8, donc tout dépend simplement de ce que l'OP entend par «le meilleur», et sur quelles bases il fonde la décision de ce qui est mieux.
Stultuske
17
Je n'aime généralement pas les ternaires imbriqués, mais cela semble assez propre.
JollyJoker
11
C'est une énorme douleur à analyser pour quiconque ne lit pas les opérateurs ternaires imbriqués tous les jours.
Cubic
2
@Cubic: Si vous pensez que c'est difficile à analyser, écrivez un commentaire comme // take first non-null of str1..3. Une fois que vous savez ce qu'il fait, il devient facile de voir comment.
Peter Cordes
4
@Cubic Pourtant, il s'agit d'un simple motif répété. Une fois que vous avez analysé la ligne 2, vous avez tout analysé, quel que soit le nombre d'observations incluses. Et une fois que vous avez terminé, vous avez appris à exprimer une sélection similaire de dix cas différents d'une manière brève et relativement simple. La prochaine fois que vous verrez une ?:échelle, vous saurez ce qu'elle fait. (Btw, les programmeurs fonctionnels connaissent cela comme des (cond ...)clauses: c'est toujours une garde suivie de la valeur appropriée à utiliser lorsque la garde est vraie.)
cmaster - reinstate monica
35

Vous pouvez également utiliser une boucle:

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

Les réponses actuelles sont bonnes, mais vous devriez vraiment mettre cela dans une méthode utilitaire:

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

Cette méthode est dans ma Utilclasse depuis des années, rend le code beaucoup plus propre:

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

Vous pouvez même le rendre générique:

@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
la source
10
FWIW, en SQL, cette fonction s'appelle coalesce , donc je l'appelle aussi dans mon code. Que cela fonctionne pour vous dépend de combien vous aimez vraiment SQL.
Tom Anderson
5
Si vous allez le mettre dans une méthode utilitaire, vous pouvez aussi bien le rendre efficace.
Todd Sewell
1
@ToddSewell, que voulez-vous dire?
Gustavo Silva
5
@GustavoSilva Todd signifie probablement que, ceci étant une de mes méthodes utilitaires, il est inutile d'utiliser Arrays.stream()quand je peux faire la même chose avec a foret a != null, ce qui est plus efficace. Et Todd aurait raison. Cependant, lorsque j'ai codé cette méthode, je cherchais un moyen de le faire en utilisant les fonctionnalités de Java 8, tout comme OP, donc il y a ça.
walen
4
@GustavoSilva Ouais, c'est fondamentalement ça: si vous voulez mettre ceci est une fonction util, les préoccupations concernant le code propre ne sont plus si importantes, et vous pouvez donc utiliser une version plus rapide.
Todd Sewell
13

J'utilise une fonction d'aide, quelque chose comme

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

Ensuite, ce type de code peut être écrit comme

String s = firstNonNull(str1, str2, str3, str4);
Michael Anderson
la source
1
Pourquoi le v0paramètre supplémentaire ?
tobias_k
1
@tobias_k Le paramètre supplémentaire lors de l'utilisation de varargs est la manière idiomatique en Java d'exiger 1 ou plusieurs arguments plutôt que 0 ou plus. (Voir le point 53 de Effective Java, Ed. 3., qui utilise mincomme exemple.) Je suis moins convaincu que cela est approprié ici.
Nick
3
@Nick Oui, je l'ai deviné, mais la fonction fonctionnerait tout aussi bien (en fait se comporterait exactement de la même manière) sans elle.
tobias_k
1
L'une des principales raisons de passer un premier argument supplémentaire est d'être explicite sur le comportement lors du passage d'un tableau. Dans ce cas, je veux firstNonNull(arr)renvoyer arr s'il n'est pas nul. Si c'était le cas, firstNonNull(T... vs)il renverrait à la place la première entrée non nulle dans arr.
Michael Anderson
4

Une solution qui peut être appliquée à autant d'éléments que vous le souhaitez peut être:

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

Vous pourriez imaginer une solution comme ci-dessous, mais la première assure non nullitypour tous les éléments

Stream.of(str1, str2, str3).....orElse(str4)
azro
la source
6
orElse en dépit str4du fait qu'il soit nul en fait
Naman
1
@nullpointer Ou orElseNull, ce qui revient au même si str4est nul.
tobias_k
3

Vous pouvez également regrouper toutes les chaînes dans un tableau de chaînes, puis effectuer une boucle for pour vérifier et rompre la boucle une fois qu'elle est affectée. En supposant que s1, s2, s3, s4 sont toutes des chaînes.

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


s = s4;

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

Modifié pour mettre la condition par défaut à s4 si aucune n'est attribuée.

danielctw
la source
Vous avez omis s4(ou str4), qui doit être attribué à sla fin, même s'il est nul.
displayName
3

Basé sur une méthode et simple.

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

Et utilisez-le comme:

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

C'est simple à faire avec des tableaux et c'est joli.

Madhusoodan P
la source
0

Si vous utilisez Apache Commons Lang 3, cela peut être écrit comme ceci:

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

L'utilisation de a ObjectUtils.firstNonNull(T...)été tirée de cette réponse . Différentes approches ont également été présentées dans des questions connexes .

lczapski
la source