【问题标题】:Use Java lambda instead of 'if else'使用 Java lambda 而不是 'if else'
【发布时间】:2018-09-26 04:31:31
【问题描述】:

使用 Java 8,我有以下代码:

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

我想转换成 lambda 样式,

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条件为真,则不需要检查ifNotExist,因为有时,exist()方法需要这么多工作量要检查),但我总是要检查两次。我怎样才能避免这种情况?

也许“存在”这个词会让人误解我的想法。你可以想象我还需要一些方法:

ifVisible()
ifEmpty()
ifHasAttribute()

很多人说这是个坏主意,但是:

在 Java 8 中,我们可以使用 lambda forEach 代替传统的 for 循环。在编程中forif 是两个基本的流控制。如果我们可以将 lambda 用于 for 循环,为什么将 lambda 用于 if 是个坏主意?

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

list.forEach(Element::doSomething);

在 Java 8 中,Optional 带有 ifPresent,类似于我对 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
}

我认为如果我们使用 lambda,我们可以避免这个错误!

【问题讨论】:

  • 出于好奇,您希望通过用 lambda 替换 if-else 语句获得什么好处? (我能想到的唯一副作用是代码执行的额外开销,对于以后必须维护这种模式的可怜的 sap 来说是最可悲的)
  • 仅供参考,如果我看到您在生产代码中这样做,我会与我们的主管/经理讨论您无法编写干净、简单的代码。在生产环境中使用if/else
  • @jpmc26 你听起来像一个梦想工作
  • @Michael 对不起,如果我对那些因为试图遵循某些原则或时尚而编写糟糕代码的人失去了耐心。大约 6 年以来,一直试图让这样的系统在轨道上运行,还有几个月的时间,当我离开时,有人将它 远远 开得离轨道更远。 (不是开玩笑,我们不得不重写在那段时间里完成的几乎所有事情,不管怎样,那些实际上已经成功的部分。)所以我尽量劝阻那些不是基于解决问题的想法,保存为尽可能多的心痛。
  • 回应其他人所说的:如果您这样做是为了练习,那很好,但请不要在实际代码中这样做。包括您自己在内的开发人员在维护现有代码上花费的时间比编写新东西要多得多,因此编写代码使其尽可能易于阅读符合您和我们的最大利益。 if/else 语句是正常且良好的。

标签: java if-statement lambda java-8


【解决方案1】:

如果您正在对对象执行简单检查,然后根据条件执行一些语句,那么一种方法是使用 MapPredicate 作为键和所需的表达式作为值 例如。

Map<Predicate<Integer>,Supplier<String>> ruleMap = new LinkedHashMap <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!");
}};

我们稍后可以流式传输以下 Map 以在 Predicate 返回 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 ......

【讨论】:

  • LinkedHashMap 应该被使用而不是HashMap。如果i=9HashMap 你不能保证i &lt; 10 会在i &lt; 100 之前被评估,所以如果你期望"Less than 10!" 你可以得到"Less than 100!"。使用LinkedHashMap,会按照您在地图中放置的顺序评估条件,因此使用i=9 将返回"Less than 10!"
【解决方案2】:

问题

(1) 你似乎混淆了不同的方面 - control flowdomain logic

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

(2) 不清楚控制流方法之后的方法(如ifExistifNotExist)应该如何表现。它们应该始终执行还是仅在条件下才被调用(类似于ifExist)?

(3) ifExist 这个名字暗示了一个终端操作,所以没有什么可以返回 - void。一个很好的例子是来自Optionalvoid ifPresent(Consumer)

解决方案

我会编写一个完全分离的类,它独立于任何具体类和任何特定条件。

接口很简单,由两个无上下文控制流方法组成 - ifTrueifFalse

有几种方法可以创建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();

更新 1:

对于其他测试方法,思路保持不变。

Element element = new Element();

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

更新 2:

重新考虑代码设计是一个很好的理由。 2 个 sn-ps 都不是很好。

假设您在e0.exist() 中需要actionC。如何更改方法引用Element::actionA

它会变回一个 lambda:

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

除非您将 actionAactionC 包装在一个方法中(这听起来很糟糕):

e0.ifExist(Element::actionAAndC);

现在的 lambda 比 if 更不“可读”。

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

但我们要为此付出多少努力?我们将付出多少努力来维护这一切?

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

【讨论】:

  • 为什么getOperand().otherElementMethod() 比条件语句后的第二条语句element.otherElementMethod() 更好?
  • 也不要使用Function&lt;E, Boolean&gt;,使用谓词
  • @Michael,这几乎是一样的,但 OP 想要形成链。关于Predicate - 你是对的,谢谢
【解决方案3】:

您可以使用需要两个消费者的单一方法:

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

【讨论】:

  • 我喜欢这个。可以简化为(exist() ? ifExist : orElse).accept(this);
  • 谢谢你的回答,这个方法很好,但实际上我想用流畅的风格,这不是我的期望
  • @yelliver 为什么想要“流利的风格”?它解决了什么问题?这个答案的优点是更简单、更直接、更容易使用。你应该有一个非常很好的理由让事情复杂化。 (请注意,这意味着您首先需要一个很好的理由,不要只使用 if/else 语法。)
  • @yelliver 我想指出,我认为这个答案更适合您编辑的示例,而不是流畅的界面。
【解决方案4】:

因为它几乎但不是真正匹配可选,也许你可以重新考虑逻辑:

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

一般来说这两个分支是不对称的。

【讨论】:

  • @Andrew 我没想到会有 4 个支持,因为我只是想表明自定义 exists 可能指向 Optional 更适合的东西。 forEach 肯定是错的,谢谢。
  • Java 9+ 中还有Optional.or
【解决方案5】:

它被称为'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
});

【讨论】:

  • OP 希望避免两次检查条件 - if the exist condition is true, no need to check ifNotExist
  • @Eran 如果它是一个布尔标志,我认为没有理由费心过度设计任何东西。不过,我已经更新了我的答案。
  • 只有 45 行,您设法节省了 -2 行。典型的 Java :D
  • @EricDuminil 好吧,接口和2个类是通用的,可重用的。
  • 是的,正是我想使用流畅的界面,但是有了你的解决方案,如果我不检查其他方法,我无法链接元素的其他方法,例如: element.ifExist(... ).otherElementMethod()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-10-02
  • 2020-10-14
  • 1970-01-01
  • 1970-01-01
  • 2011-09-06
  • 1970-01-01
相关资源
最近更新 更多