web-dev-qa-db-ja.com

'if else'の代わりにJava lambdaを使用します

Java 8では、次のコードがあります。

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

ラムダスタイルに変換したい

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

次のようなifExistメソッドを使用します。

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

しかし今、私は他に電話するケースがあります:

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

同様のifNotExistを記述できますが、それらは相互に排他的である必要があります(exist条件がtrueの場合、ifNotExistを確認する必要はありません。チェックするには非常に多くの作業負荷がかかります)が、私は常に2回チェックする必要があります。どうすればそれを回避できますか?

「既存の」言葉が誰かに私の考えを誤解させるかもしれません。私もいくつかの方法が必要だと想像できます:

 ifVisible()
 ifEmpty()
 ifHasAttribute()

多くの人がこれは悪い考えだと言ったが、:

Java 8では、従来のforループの代わりにラムダforEachを使用できます。プログラミングでは、forifは2つの基本的なフロー制御です。 forループにラムダを使用できる場合、なぜifループにラムダを使用するのは悪い考えですか?

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

list.forEach(Element::doSomething);

Java 8には、ifPresentを持つOptionalがあり、ifExistの私の考えと同様です。

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

そして、コードの保守と読みやすさについて、単純なif句を繰り返し使用する次のコードがあるとしたらどう思いますか?

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

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

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

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

と比較:

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

どちらが良いですか?そして、おっと、従来のif句のコードには間違いがあることに気づきましたか:

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

ラムダを使用すれば、この間違いを回避できると思います!

29
yelliver

実際にはほとんど一致しませんが、オプションの場合、ロジックを再検討することができます。

Java 8の表現力は限られています。

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

ここで、mapは要素の表示を制限する可能性があります。

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

Java 9:

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

一般に、2つのブランチは非対称です。

33
Joop Eggen

'fluent interface' と呼ばれます。戻り値の型とreturn this;を変更するだけで、メソッドを連鎖できます。

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;
}

あなたは少し手の込んだものを得て、中間型を返すことができます:

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);
}

サンプル使用法:

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

2つのコンシューマーを取る単一のメソッドを使用できます。

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

次に、それを呼び出します:

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

問題

(1)さまざまな側面を混同しているようです- 制御フロードメインロジック

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

(2)制御フローメソッド(ifExistifNotExistなど)の後のメソッドの動作が不明です。それらは常に実行されるべきですか、それとも条件の下でのみ呼び出されるべきですか(ifExistと同様)?

(3)名前ifExistは端末操作を意味するため、返すものはありません-void。良い例は void ifPresent(Consumer) from Optional です。

ソリューション

具体的なクラスや特定の条件に依存しない完全に分離されたクラスを作成します。

インターフェイスはシンプルで、2つのコンテキストレス制御フローメソッド-ifTrueおよびifFalseで構成されています。

Conditionオブジェクトを作成するにはいくつかの方法があります。インスタンス(例:element)および条件(例: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;
    }

}

さらに、ConditionElementに統合できます。

class Element {

    ...

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

}

私が推進しているパターンは次のとおりです。

  • Elementを使用します。
  • Conditionを取得します。
  • Conditionによってフローを制御します。
  • Elementに戻ります。
  • Elementを引き続き使用します。

結果

Condition.ofによるConditionの取得:

Element element = new Element();

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

Element#formConditionによるConditionの取得:

Element element = new Element();

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

更新1:

他のテスト方法については、考え方は同じままです。

Element element = new Element();

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

アップデート2:

コード設計を再考するのは十分な理由です。 2つのスニペットはどちらも素晴らしいものではありません。

e0.exist()内にactionCが必要だと想像してください。メソッド参照Element::actionAはどのように変更されますか?

ラムダに戻ります:

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

actionAactionCを単一のメソッドでラップしない限り(ひどく聞こえます):

e0.ifExist(Element::actionAAndC);

ラムダは、ifの場合よりもさらに「読みにくく」なりました。

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

しかし、それを行うためにどれだけの努力をしますか?そして、それをすべて維持するためにどれだけの労力を注ぎますか?

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

オブジェクトに対して単純なチェックを実行し、条件に基づいていくつかのステートメントを実行する場合、1つのアプローチは、たとえば、キーとして述語、値として目的の式を持つマップを持つことです。

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!");
}};

述語がtrueを返した場合、すべてのif/elseコードを置き換えることができる値を取得するために、後で次のマップをストリーミングできます。

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

FindFirst()を使用しているため、if/else if/else if ......と同等です。

1
ag157