50 votes

Traitement des listes en Java 8 - ajouter des éléments de manière conditionnelle

J'ai le morceau de code suivant :

List<Object> list = new ArrayList<>();
list.addAll(method1());
if(list.isEmpty()) { list.addAll(method2()); }
if(list.isEmpty()) { list.addAll(method3()); }
if(list.isEmpty()) { list.addAll(method4()); }
if(list.isEmpty()) { list.addAll(method5()); }
if(list.isEmpty()) { list.addAll(method6()); }
return list;

Existe-t-il un moyen agréable d'ajouter des éléments de manière conditionnelle, peut-être en utilisant des opérations de flux ? J'aimerais ajouter les éléments de la méthode 2 seulement si la liste est vide, sinon retour et ainsi de suite.

Edit : Il est utile de mentionner que les méthodes contiennent une logique lourde et qu'il faut empêcher leur exécution.

0 votes

Qu'est-ce que les méthodes retournent comme objet, exactement ?

68voto

Dorian Gray Points 1473

Vous pouvez essayer de vérifier la valeur de retour de addAll . Il retournera true chaque fois que la liste a été modifiée, alors essayez ceci :

List<Object> list = new ArrayList<>();
// ret unused, otherwise it doesn't compile
boolean ret = list.addAll(method1())
    || list.addAll(method2()) 
    || list.addAll(method3())
    || list.addAll(method4())
    || list.addAll(method5())
    || list.addAll(method6());
return list;

En raison de l'évaluation paresseuse, la première addAll L'opération qui a ajouté au moins un élément empêchera le reste d'être appelé. J'aime le fait que "||" exprime assez bien l'intention.

5 votes

Un des rares cas où l'on se préoccupe de la valeur de retour de addAll . en effet très intelligente, efficace et lisible ;-).

2 votes

Je suis d'accord. Je crois qu'il n'y a pas plus simple et plus lisible que cela.

5 votes

@Aomine Je ne trouve pas du tout l'utilisation abusive des expressions booléennes pour imiter les exécutions ordonnées lisible et maintenable. Le prochain meilleur gars pourrait réordonner les expressions parce que "c'est une expression OR, l'ordre ne devrait pas avoir d'importance" et boom vous avez un bug difficile à trouver. Le commentaire nécessaire sur le fait d'avoir une variable juste pour que cela compile est, selon moi, un indice de mauvaise conception également. Donc, bien que cela réponde d'une certaine manière à la question, je ne recommanderais pas de l'utiliser dans du code de production.

45voto

ernest_k Points 14807

Je voudrais simplement utiliser un flux de fournisseurs et filtrer sur List.isEmpty :

Stream.<Supplier<List<Object>>>of(() -> method1(), 
                                  () -> method2(), 
                                  () -> method3(), 
                                  () -> method4(), 
                                  () -> method5(), 
                                  () -> method6())
    .map(Supplier<List<Object>>::get)
    .filter(l -> !l.isEmpty())
    .findFirst()
    .ifPresent(list::addAll);

return list;

findFirst() évitera les appels inutiles à methodN() lorsque la première liste non vide est retournée par l'une des méthodes.

EDITAR:
Comme indiqué dans les commentaires ci-dessous, si votre list n'est pas initialisé avec autre chose, alors il est logique de retourner directement le résultat du flux :

return  Stream.<Supplier<List<Object>>>of(() -> method1(), 
                                          () -> method2(), 
                                          () -> method3(), 
                                          () -> method4(), 
                                          () -> method5(), 
                                          () -> method6())
    .map(Supplier<List<Object>>::get)
    .filter(l -> !l.isEmpty())
    .findFirst()
    .orElseGet(ArrayList::new);

5 votes

Il convient de noter que, si methodX() renvoie un List et non un autre type de Collection il pourrait être possible de renvoyer cette liste directement, au lieu de créer une nouvelle liste et de l'enrichir : .map(Supplier::get).filter(s -> !s.isEmpty()).findFirst().orElse(emptyList()); . Il n'est pas possible de déterminer si cela est approprié ou non à partir de la question.

0 votes

Au fait, savez-vous pourquoi vous devez indiquer explicitement le type <Supplier<List<Object>>> à la méthode de l'usine parce que le compilateur ne peut pas déduire le type lui-même ? J'ai eu le même problème.

1 votes

@Ricola Sans le définir explicitement, le compilateur ne connaît pas le type cible des expressions lambda. L'alternative aurait été de le faire en deux étapes, en déclarant explicitement un Stream<Supplier<List<Object>>> comme type de flux.

17voto

JB Nizet Points 250258

Une façon de le faire sans se répéter est d'extraire une méthode qui le fait pour vous :

private void addIfEmpty(List<Object> targetList, Supplier<Collection<?>> supplier) {
    if (targetList.isEmpty()) {
        targetList.addAll(supplier.get());
    }
}

Et puis

List<Object> list = new ArrayList<>();
addIfEmpty(list, this::method1);
addIfEmpty(list, this::method2);
addIfEmpty(list, this::method3);
addIfEmpty(list, this::method4);
addIfEmpty(list, this::method5);
addIfEmpty(list, this::method6);
return list;

Ou même utiliser une boucle for :

List<Supplier<Collection<?>>> suppliers = Arrays.asList(this::method1, this::method2, ...);
List<Object> list = new ArrayList<>();
suppliers.forEach(supplier -> this.addIfEmpty(list, supplier));

Maintenant, DRY n'est pas l'aspect le plus important. Si vous pensez que votre code original est plus facile à lire et à comprendre, alors gardez-le ainsi.

0 votes

Vous devriez plutôt nommer la méthode addIfEmpty au lieu de addIfNotEmpty

12voto

Ricola Points 1927

Vous pourriez rendre votre code plus agréable en créant la méthode

public void addAllIfEmpty(List<Object> list, Supplier<List<Object>> method){
    if(list.isEmpty()){
        list.addAll(method.get());
    }
}

Ensuite, vous pouvez l'utiliser comme suit (j'ai supposé que vos méthodes ne sont pas des méthodes statiques, si c'est le cas, vous devez les référencer à l'aide de la fonction ClassName::method1 )

List<Object> list = new ArrayList<>();
list.addAll(method1());
addAllIfEmpty(list, this::method2);
addAllIfEmpty(list, this::method3);
addAllIfEmpty(list, this::method4);
addAllIfEmpty(list, this::method5);
addAllIfEmpty(list, this::method6);
return list;

Si vous voulez vraiment utiliser un Stream, vous pouvez faire ceci

 Stream.<Supplier<List<Object>>>of(this::method1, this::method2, this::method3, this::method4, this::method5, this::method6)
                .collect(ArrayList::new, this::addAllIfEmpty, ArrayList::addAll);

En fonction de la manière dont vos méthodes sont référencées, il peut être préférable d'utiliser une boucle.<br>

6voto

Aomine Points 42709

Vous pourriez créer une méthode de ce type :

public static List<Object> lazyVersion(Supplier<List<Object>>... suppliers){
      return Arrays.stream(suppliers)
                .map(Supplier::get)
                .filter(s -> !s.isEmpty()) // or .filter(Predicate.not(List::isEmpty)) as of JDK11
                .findFirst()
                .orElseGet(Collections::emptyList);
}

puis l'appeler comme suit :

lazyVersion(() -> method1(),
            () -> method2(),
            () -> method3(),
            () -> method4(),
            () -> method5(),
            () -> method6());

nom de la méthode à des fins d'illustration uniquement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X