web-dev-qa-db-fra.com

Utilisez Java lambda au lieu de 'if else'

Avec Java 8, j'ai ce code:

if(element.exist()){
    // Do something
}

Je veux convertir au style lambda,

element.ifExist(el -> {
    // Do something
});

avec une méthode ifExist comme celle-ci:

public void ifExist(Consumer<Element> consumer) {
    if (exist()) {
        consumer.accept(this);
    }
}

Mais maintenant, j'ai d'autres cas à appeler:

element.ifExist(el -> {
    // Do something
}).ifNotExist(el -> {
    // Do something
});

Je peux écrire un ifNotExist similaire, et je veux qu'elles s'excluent mutuellement (si la condition exist est vraie, il n'est pas nécessaire de vérifier ifNotExist, car parfois, la méthode exist () prend tellement de travail à vérifier), mais je dois toujours vérifier deux fois. Comment puis-je éviter cela?

Peut-être que le mot "exist" fait que quelqu'un comprend mal mon idée. Vous pouvez imaginer que j'ai aussi besoin de méthodes:

 ifVisible()
 ifEmpty()
 ifHasAttribute()

Beaucoup de gens ont dit que c'était une mauvaise idée, mais:

Dans Java 8, nous pouvons utiliser lambda forEach au lieu d'une boucle traditionnelle for. En programmation for et if sont deux commandes de flux de base. Si nous pouvons utiliser lambda pour une boucle for, pourquoi utiliser lambda pour if mauvaise idée?

for (Element element : list) {
    element.doSomething();
}

list.forEach(Element::doSomething);

Dans Java 8, il y a Optional avec ifPresent, semblable à mon idée de ifExist:

Optional<Elem> element = ...
element.ifPresent(el -> System.out.println("Present " + el);

Et en ce qui concerne la maintenance et la lisibilité du code, que pensez-vous si le code suivant contient de nombreuses clauses répétitives simples if?

    if (e0.exist()) {
        e0.actionA();
    } else {
        e0.actionB();
    }

    if (e1.exist()) {
        e0.actionC();
    }

    if (e2.exist()) {
        e2.actionD();
    }

    if (e3.exist()) {
        e3.actionB();
    }

Comparer aux:

    e0.ifExist(Element::actionA).ifNotExist(Element::actionB);
    e1.ifExist(Element::actionC);
    e2.ifExist(Element::actionD);
    e3.ifExist(Element::actionB);

Ce qui est mieux? Et, oups, avez-vous remarqué que dans le code de clause if traditionnel, il y a une erreur dans:

if (e1.exist()) {
    e0.actionC(); // Actually e1
}

Je pense que si nous utilisons lambda, nous pouvons éviter cette erreur!

29
yelliver

Comme cela correspond presque mais pas vraiment Facultatif, vous pourriez peut-être reconsidérer la logique:

Java 8 a une expressivité limitée:

Optional<Elem> element = ...
element.ifPresent(el -> System.out.println("Present " + el);
System.out.println(element.orElse(DEFAULT_ELEM));

Ici, la map pourrait restreindre l'affichage de l'élément:

element.map(el -> el.mySpecialView()).ifPresent(System.out::println);

Java 9:

element.ifPresentOrElse(el -> System.out.println("Present " + el,
                        () -> System.out.println("Not present"));

En général, les deux branches sont asymétriques.

33
Joop Eggen

C'est ce qu'on appelle 'interface fluide' . Modifiez simplement le type de retour et return this; pour vous permettre d’enchaîner les méthodes:

public MyClass ifExist(Consumer<Element> consumer) {
    if (exist()) {
        consumer.accept(this);
    }
    return this;
}

public MyClass ifNotExist(Consumer<Element> consumer) {
    if (!exist()) {
        consumer.accept(this);
    }
    return this;
}

Vous pourriez avoir un peu plus sophistiqué et retourner un type intermédiaire:

interface Else<T>
{
    public void otherwise(Consumer<T> consumer); // 'else' is a keyword
}

class DefaultElse<T> implements Else<T>
{
    private final T item;

    DefaultElse(final T item) { this.item = item; }

    public void otherwise(Consumer<T> consumer)
    {
        consumer.accept(item);
    }
}

class NoopElse<T> implements Else<T>
{
    public void otherwise(Consumer<T> consumer) { }
}

public Else<MyClass> ifExist(Consumer<Element> consumer) {
    if (exist()) {
        consumer.accept(this);
        return new NoopElse<>();
    }
    return new DefaultElse<>(this);
}

Exemple d'utilisation:

element.ifExist(el -> {
    //do something
})
.otherwise(el -> {
    //do something else
});
19
Michael

Vous pouvez utiliser une seule méthode prenant deux consommateurs:

public void ifExistOrElse(Consumer<Element> ifExist, Consumer<Element> orElse) {
    if (exist()) {
        ifExist.accept(this);
    } else {
        orElse.accept(this);
    }
}

Puis appelez-le avec:

element.ifExistOrElse(
  el -> {
    // Do something
  },
  el -> {
    // Do something else
  });
12
Eran

Le problème

(1) Vous semblez mélanger différents aspects - flux de contrôle et logique de domaine .

element.ifExist(() -> { ... }).otherElementMethod();
          ^                      ^
        control flow method     business logic method

(2) Il est difficile de savoir comment les méthodes après une méthode de flux de contrôle (telle que ifExist, ifNotExist) devraient se comporter. Devraient-ils toujours être exécutés ou être appelés uniquement sous la condition (similaire à ifExist)?

(3) Le nom ifExist implique une opération de terminal. Il n'y a donc rien à renvoyer - void. Un bon exemple est void ifPresent(Consumer) de Optional .

La solution

J'écrirais une classe complètement séparée qui serait indépendante de toute classe concrète et de toute condition spécifique.

L'interface est simple et comprend deux méthodes de flux de contrôle sans contexte: ifTrue et ifFalse.

Il existe plusieurs façons de créer un objet Condition. J'ai écrit une méthode de fabrique statique pour votre instance (par exemple element) et votre condition (par exemple Element::exist).

public class Condition<E> {

    private final Predicate<E> condition;
    private final E operand;

    private Boolean result;

    private Condition(E operand, Predicate<E> condition) {
        this.condition = condition;
        this.operand = operand;
    }

    public static <E> Condition<E> of(E element, Predicate<E> condition) {
        return new Condition<>(element, condition);
    }

    public Condition<E> ifTrue(Consumer<E> consumer) {
        if (result == null)
            result = condition.test(operand);

        if (result)
            consumer.accept(operand);

        return this;
    }

    public Condition<E> ifFalse(Consumer<E> consumer) {
        if (result == null)
            result = condition.test(operand);

        if (!result)
            consumer.accept(operand);

        return this;
    }

    public E getOperand() {
        return operand;
    }

}

De plus, nous pouvons intégrer Condition dans Element:

class Element {

    ...

    public Condition<Element> formCondition(Predicate<Element> condition) {
        return Condition.of(this, condition);
    }

}

Le modèle que je promeut est:

  • travailler avec un Element;
  • obtenir un Condition;
  • contrôler le flux par le Condition;
  • revenez à la Element;
  • continuez à travailler avec Element.

Le résultat

Obtention de Condition by Condition.of:

Element element = new Element();

Condition.of(element, Element::exist)
             .ifTrue(e -> { ... })
             .ifFalse(e -> { ... })
         .getOperand()
             .otherElementMethod();

Obtention de Condition by Element#formCondition:

Element element = new Element();

element.formCondition(Element::exist)
           .ifTrue(e -> { ... })
           .ifFalse(e -> { ... })
       .getOperand()
           .otherElementMethod();

Mise à jour 1:

Pour d'autres méthodes de test, l'idée reste la même.

Element element = new Element();

element.formCondition(Element::isVisible);
element.formCondition(Element::isEmpty);
element.formCondition(e -> e.hasAttribute(ATTRIBUTE));

Mise à jour 2:

C'est une bonne raison de repenser la conception du code. Aucun des 2 extraits est génial.

Imaginez que vous ayez besoin de actionC dans e0.exist(). Comment la référence à la méthode Element::actionA sera-t-elle modifiée?

Ce serait redevenu un lambda:

e0.ifExist(e -> { e.actionA(); e.actionC(); });

sauf si vous enveloppez actionA et actionC dans une seule méthode (ce qui peut sembler horrible):

e0.ifExist(Element::actionAAndC);

Le lambda est maintenant moins lisible que le if.

e0.ifExist(e -> {
    e0.actionA();
    e0.actionC();
});

Mais combien ferions-nous pour faire cela? Et combien d'efforts allons-nous déployer pour maintenir tout cela?

if(e0.exist()) {
    e0.actionA();
    e0.actionC();
}
3
Andrew Tobilko

Si vous effectuez une vérification simple sur un objet, puis exécutez certaines instructions en fonction de la condition, une approche consiste à définir une carte avec un prédicat comme clé et une expression souhaitée comme valeur, par exemple.

Map<Predicate<Integer>,Supplier<String>> ruleMap = new HashMap<Predicate<Integer>,Supplier<String>>(){{
put((i)-> i<10,()->"Less than 10!");
put((i)-> i<100,()->"Less than 100!");
put((i)-> i<1000,()->"Less than 1000!");
}};

Nous pourrions ultérieurement diffuser la carte suivante pour obtenir la valeur lorsque le prédicat renvoie true, ce qui pourrait remplacer tout le code if/else

        ruleMap.keySet()
               .stream()
               .filter((keyCondition)->keyCondition.test(numItems,version))
               .findFirst()
               .ifPresent((e)-> System.out.print(ruleMap.get(e).get()));

Puisque nous utilisons findFirst (), cela équivaut à if/else if/else if ......

1
ag157