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を使用できます。プログラミングでは、for
とif
は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
}
ラムダを使用すれば、この間違いを回避できると思います!
実際にはほとんど一致しませんが、オプションの場合、ロジックを再検討することができます。
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つのブランチは非対称です。
'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
});
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
});
(1)さまざまな側面を混同しているようです- 制御フロー と ドメインロジック 。
element.ifExist(() -> { ... }).otherElementMethod();
^ ^
control flow method business logic method
(2)制御フローメソッド(ifExist
、ifNotExist
など)の後のメソッドの動作が不明です。それらは常に実行されるべきですか、それとも条件の下でのみ呼び出されるべきですか(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;
}
}
さらに、Condition
をElement
に統合できます。
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();
他のテスト方法については、考え方は同じままです。
Element element = new Element();
element.formCondition(Element::isVisible);
element.formCondition(Element::isEmpty);
element.formCondition(e -> e.hasAttribute(ATTRIBUTE));
コード設計を再考するのは十分な理由です。 2つのスニペットはどちらも素晴らしいものではありません。
e0.exist()
内にactionC
が必要だと想像してください。メソッド参照Element::actionA
はどのように変更されますか?
ラムダに戻ります:
e0.ifExist(e -> { e.actionA(); e.actionC(); });
actionA
とactionC
を単一のメソッドでラップしない限り(ひどく聞こえます):
e0.ifExist(Element::actionAAndC);
ラムダは、if
の場合よりもさらに「読みにくく」なりました。
e0.ifExist(e -> {
e0.actionA();
e0.actionC();
});
しかし、それを行うためにどれだけの努力をしますか?そして、それをすべて維持するためにどれだけの労力を注ぎますか?
if(e0.exist()) {
e0.actionA();
e0.actionC();
}
オブジェクトに対して単純なチェックを実行し、条件に基づいていくつかのステートメントを実行する場合、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 ......と同等です。