Comment obtenir la première valeur non nulle en Java?


154

Existe-t-il un équivalent Java de la COALESCEfonction SQL ? Autrement dit, existe-t-il un moyen de renvoyer la première valeur non nulle de plusieurs variables?

par exemple

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

Je veux en quelque sorte une déclaration qui renverra la première valeur non nulle a, bet c- dans ce cas, il retournerait bou 4.4. (Quelque chose comme la méthode sql - return COALESCE(a,b,c)). Je sais que je peux le faire explicitement avec quelque chose comme:

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

Mais je me suis demandé s'il y avait une fonction intégrée et acceptée pour accomplir cela.


3
Vous ne devriez pas avoir besoin d'une fonction comme celle-ci car vous ne calculeriez généralement pas «c» si «b» a la réponse que vous voulez. c'est-à-dire que vous ne construiriez pas une liste de réponses possibles uniquement pour en garder une.
Peter Lawrey

Attention: tous les RDBMS ne sont pas en court-circuit sur COALESCE. Oracle n'a commencé à le faire que récemment.
Adam Gent

3
@ BrainSlugs83 Sérieusement? Java devrait?
Dmitry Ginzburg

Réponses:


108

Non, il n'y en a pas.

Le plus proche que vous pouvez obtenir est:

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

Pour des raisons d'efficacité, vous pouvez gérer les cas courants comme suit:

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
les raisons d'efficacité que j'ai mentionnées ci-dessus sont qu'une allocation de tableau se produira chaque fois que vous invoquerez la version var arg de la méthode. cela pourrait être un gaspillage pour des articles remplis à la main, ce qui, je suppose, sera un usage courant.
les2

Cool. Merci. Dans ce cas, je m'en tiendrai probablement aux opérateurs conditionnels imbriqués dans ce cas car c'est la seule fois où il doit être utilisé et la méthode définie par l'utilisateur serait
exagérée

8
Je le retirerais toujours dans une méthode d'aide privée plutôt que de laisser un bloc conditionnel "effrayant" dans le code - "qu'est-ce que cela fait?" de cette façon, si jamais vous avez besoin de l'utiliser à nouveau, vous pouvez utiliser les outils de refactoring de votre IDE pour déplacer la méthode vers la classe utilitaire. avoir la méthode nommée aide à documenter l'intention du code, ce qui est toujours une bonne chose, IMO. (et la surcharge de la version non var-args est probablement à peine mesurable.)
les2

10
Attention: In coalesce(a, b), si best une expression complexe et ane l'est pas null, best toujours évalué. Ce n'est pas le cas pour l'opérateur conditionnel?:. Voyez cette réponse .
Pang

cela nécessite que chaque argument soit précalculé avant l'appel à la fusion, inutile pour des raisons de performances
Ivan G.


59

S'il n'y a que deux variables à vérifier et que vous utilisez Guava, vous pouvez utiliser MoreObjects.firstNonNull (T d'abord, T ensuite) .


49
Objects.firstNonNull ne prend que deux arguments; il n'y a pas d'équivalent varargs dans Guava. En outre, il lève une NullPointerException si les deux arguments sont nuls - cela peut être souhaitable ou non.

2
Bon commentaire, Jake. Cette NullPointerException limite souvent l'utilisation d'Objects.firstNonNull. Cependant, l'approche de Guava consiste à éviter les valeurs nulles.
Anton Shchastnyi

4
Cette méthode est désormais obsolète et l'alternative recommandée est MoreObjects.firstNonNull
davidwebster48

1
Si NPE est indésirable, alors voyez cette réponse
OrangeDog

51

S'il n'y a que deux références à tester et que vous utilisez Java 8, vous pouvez utiliser

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

Si vous importez statique Facultatif, l'expression n'est pas trop mauvaise.

Malheureusement, votre cas avec "plusieurs variables" n'est pas possible avec une méthode optionnelle. Au lieu de cela, vous pouvez utiliser:

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

Suite à la réponse de LES2, vous pouvez éliminer certaines répétitions dans la version efficace, en appelant la fonction surchargée:

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 pour joli. Je ne suis pas sûr des avantages en termes d'efficacité par rapport à la boucle simple, mais si vous voulez gagner en efficacité de cette façon, cela pourrait tout aussi bien être joli.
Carl Manaster

3
de cette façon, il est beaucoup moins pénible et moins sujet aux erreurs d'écrire les variantes surchargées!
les2

2
L'intérêt de la version efficace était de ne pas gaspiller de mémoire en allouant un tableau en utilisant varargs. Ici, vous gaspillez de la mémoire en créant un cadre de pile pour chaque coalesce()appel imbriqué . L'appel coalesce(a, b, c, d, e)crée jusqu'à 3 cadres de pile à calculer.
Luke

10

Cette situation nécessite un préprocesseur. Parce que si vous écrivez une fonction (méthode statique) qui sélectionne la première valeur non nulle, elle évalue tous les éléments. C'est un problème si certains éléments sont des appels de méthode (peuvent être des appels de méthode coûteux en temps). Et ces méthodes sont appelées même si un élément avant elles n'est pas nul.

Certains fonctionnent comme ça

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

devrait être utilisé mais avant la compilation en code octet, il devrait y avoir un préprocesseur qui trouve les utilisations de cette «fonction de coalescence» et la remplace par une construction comme

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

Mise à jour 02/09/2014:

Grâce à Java 8 et Lambdas, il est possible d'avoir une véritable fusion en Java! Y compris la caractéristique cruciale: les expressions particulières ne sont évaluées que lorsque cela est nécessaire - si la précédente n'est pas nulle, les suivantes ne sont pas évaluées (les méthodes ne sont pas appelées, le calcul ou les opérations disque / réseau ne sont pas effectués).

J'ai écrit un article à ce sujet Java 8: coalesce - hledáme neNULLové hodnoty - (écrit en tchèque, mais j'espère que les exemples de code sont compréhensibles pour tout le monde).


1
Bel article - ce serait bien de l'avoir en anglais, cependant.
quantum

1
Il y a quelque chose dans cette page de blog qui ne fonctionne pas avec Google Traduction. :-(
HairOfTheDog

5

Avec Guava, vous pouvez faire:

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

qui ne jette pas de NPE si les deux aet bsontnull .

EDIT: je me suis trompé, il jette NPE. La bonne manière, comme le commente Michal Čizmazia, est:

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

1
Hé, ça le fait:java.lang.NullPointerException: use Optional.orNull() instead of Optional.or(null)
Michal Čizmazia

1
Cela fait l'affaire:Optional.fromNullable(a).or(Optional.fromNullable(b)).orNull()
Michal Čizmazia

4

Pour être complet, le cas «plusieurs variables» est en effet possible, mais pas du tout élégant. Par exemple, pour les variables o, pet q:

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

Veuillez noter qu'il est utile de orElseGet()s'occuper du cas qui o, pet qne sont pas des variables mais des expressions coûteuses ou avec des effets secondaires indésirables.

Dans le cas le plus général 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

Cela peut générer des expressions excessivement longues. Cependant, si nous essayons de passer à un monde sans null, alors v[i]sont très probablement déjà de typeOptional<String> , par opposition à simplement String. Dans ce cas,

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

ou dans le cas d'expressions:

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

De plus, si vous prépare également à un style fonctionnel déclarative, o, pet qdevrait être de typeSupplier<String> comme dans:

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

Et puis le tout coalesce réduit simplement à o.get().

Pour un exemple plus concret:

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() et ageFromInput()reviendraient déjà Optional<Integer>, naturellement.

Et puis le coalescedevienteffectiveAge.get() ou simplement effectiveAgesi nous sommes satisfaits d'un Supplier<Integer>.

IMHO, avec Java 8, nous verrons de plus en plus de code structuré comme celui-ci, car il est extrêmement auto-explicatif et efficace en même temps, en particulier dans les cas plus complexes.

Je manque une classe Lazy<T>qui n'invoque Supplier<T>qu'une seule fois, mais paresseusement, ainsi que la cohérence dans la définition de Optional<T>(ie Optional<T>- Optional<T>opérateurs, ou même Supplier<Optional<T>>).


4

Vous pouvez essayer ceci:

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

Basé sur cette réponse


3

Que diriez-vous d'utiliser des fournisseurs lorsque vous voulez éviter d'évaluer une méthode coûteuse?

Comme ça:

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

Et puis en l'utilisant comme ceci:

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

Vous pouvez également utiliser des méthodes surchargées pour les appels avec deux, trois ou quatre arguments.

De plus, vous pouvez également utiliser des flux avec quelque chose comme ceci:

public static <T> T coalesce2(Supplier<T>... s) {
    return Arrays.stream(s).map(Supplier::get).filter(Objects::nonNull).findFirst().orElse(null);
}

Pourquoi envelopper le premier argument dans un Suppliers'il sera inspecté de toute façon? Par souci d'uniformité?
Inego

0

Que diriez-vous:

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

Java ArrayList autorise commodément les entrées nulles et cette expression est cohérente quel que soit le nombre d'objets à prendre en compte. (Sous cette forme, tous les objets considérés doivent être du même type.)


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

2
Dieu, je déteste les génériques. J'ai vu ce que vous vouliez dire dès le départ. J'ai dû regarder deux fois @ LES2 pour comprendre qu'il faisait la même chose (et probablement "mieux")! +1 pour plus de clarté
Bill K

Ouais, les génériques sont la voie à suivre. Mais je ne suis pas tout à fait familier avec les subtilités.
Eric

10
Il est temps d'apprendre les génériques :-). Il y a peu de différence entre l'exemple de @ LES2 et celui-ci, à part T au lieu d'Object. -1 pour créer une fonction qui forcera le cast de la valeur de retour à Double. Aussi pour nommer une méthode Java en majuscules, ce qui peut convenir en SQL, mais ce n'est pas un bon style en Java.
Avi

1
Je réalise que le tout en majuscules est une mauvaise pratique. Je montrais juste à l'OP comment écrire une fonction sous le nom demandé. D'accord, le casting Doubleest loin d'être idéal. Je ne savais tout simplement pas que les fonctions statiques pouvaient recevoir des paramètres de type. Je pensais que c'était juste des cours.
Eric
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.