【问题标题】:Optional vs if/else-if performance java 8可选与 if/else-if 性能 java 8
【发布时间】:2019-05-21 09:28:58
【问题描述】:

您好,我有两个代码示例

if/else if/else 语句

private Object getObj(message) {
        if (message.getA() != null)
            return message.getA();
        else if (message.getB() != null)
            return message.getB();
        else if (message.getC() != null)
            return message.getC();
        else return null;
}

可选语句

private Optional<Object> wrap(Object o){
    return Optional.ofNullable(o);
}

private Object getObj(message) {
    return  wrap(message.getA())
            .orElseGet(() -> wrap(message.getB())
            .orElseGet(() -> wrap(message.getC())
            .orElse(null)));
}

所以我的问题是这两者在性能方面的比较(我在实际代码中有大约 15-20 个 if-else 语句)?

是否值得重构代码的可读性与性能,或者是对选项的滥用?

如果 if/else-if 语句增长到 100+,那么性能损失是多少?

提前致谢

【问题讨论】:

  • 我个人觉得链接更容易阅读 - 但我的同事认为我很奇怪,所以 这取决于 我猜。性能方面,这与您 99% 无关;除非分析器证明不是这样
  • 是否值得重构代码的可读性与性能,或者是对选项的滥用?这取决于您的需求
  • 如果你不知道premature optimization是什么,请查一下。
  • 仅供参考,Java 9 有 Optional#or 方法来避免你正在做的 wrap
  • @wilmol or 很好,但在这种情况下并不能将您从wrap 中拯救出来。语法为return wrap(message.getA()) .or(() -&gt; wrap(message.getB())) .or(() -&gt; wrap(message.getC())) .orElse(null);。括号的放置略有不同。 wrap 使用相同。

标签: java performance if-statement java-8 optional


【解决方案1】:

不要将Optionals 用于条件逻辑。

它们是设计的,to be returned from a method to indicate a potentially absent value

仅仅因为你可以很好地将它们链接成一行并不意味着它是可以理解的。此外,您实际上一无所获。性能开销可能很大。在最坏的情况下,N 对象被创建然后被丢弃。留在你的“正常”if-else 连锁店。


与其想办法让你当前的代码更具可读性,不如退后一步问问自己为什么你需要 15-20 个 if-else 语句。你能分解一些逻辑吗?为什么首先需要一个 getter 来处理这么多可能具有不同类型的不同字段?等等。

【讨论】:

  • 感谢 Lino 的输入,实际上这是一个需要进行所有这些检查的核心验证方法(我知道这可能是一个职责分离问题,但目前重构逻辑非常繁琐)
  • @PanosK 如果您可能会在您的问题中包含更具体的原因为什么需要这样做(呼叫站点、示例等),那么我可能会给出更具体的答案。
  • 我想我得到了答案“不要将 Optionals 用于条件逻辑”,我会坚持使用 if/else 并尝试重构逻辑以在未来减少它们。谢谢 Lino(我的问题仍然没有得到反对)。
【解决方案2】:

还有第三种形式(仍然允许一些变化)。

return Stream.<Supplier<Object>>of(message::getA, message::getB, message::getC)
        .map(Supplier::get)
        .filter(Objects::nonNull)
        .findFirst()
        .orElse(null);

目前可能是最不灵活和效率最低的,但很清楚。

【讨论】:

  • 你必须写 Stream.&lt;Supplier&lt;Object&gt;&gt;of(…) 才能编译。
  • 我注意到,在流中使用供应商使得对此的评估与问题中一样懒惰:getB 仅在getA 返回null 时才被调用,等等。喜欢。
【解决方案3】:

tl;博士

如果您的目标是压缩代码,请使用三元链。性能可能与一系列 if-then-else 语句的性能相同。

        ( this.getA() != null ) ? this.getA()
                : ( this.getB() != null ) ? this.getB()
                : ( this.getC() != null ) ? this.getC()
                : null;

三元链

正如Answer by Lino 正确指出的那样,您正试图使Optional 超出其最初的设计目的(在lambda 和流中返回值)。通常最好仅将Optionalreturn 语句一起使用,并且只有在您想明确null 是要返回的有效值时才使用。请参阅 Brian Goetz 的 this Answer

ternary operator 是一个浓缩的 if-then-else,组合成一个单行。

result = test ? valueToUseIfTestIsTrue : valueToUseIfTestIsFalse

例子:

Color color = isPrinterMonochrome ? Color.GREY : Color.GREEN ; 

使用三元语句链。

所以这个:

    if ( this.getA() != null )
        return this.getA();
    else if ( this.getB() != null )
        return this.getB();
    else if ( this.getC() != null )
        return this.getC();
    else return null;

…变成这样:

    return
            ( this.getA() != null ) ? this.getA()
                    : ( this.getB() != null ) ? this.getB()
                    : ( this.getC() != null ) ? this.getC()
                    : null;

示例代码。

public String getA ()
{
    // return "A";
    return null;
}

public String getB ()
{
    // return "B";
    return null;
}

public String getC ()
{
    return "C";
    // return null;
}

public String getABC ()
{
    if ( this.getA() != null )
        return this.getA();
    else if ( this.getB() != null )
        return this.getB();
    else if ( this.getC() != null )
        return this.getC();
    else return null;
}

public String getABCTernary ()
{
    return
            ( this.getA() != null ) ? this.getA()
                    : ( this.getB() != null ) ? this.getB()
                    : ( this.getC() != null ) ? this.getC()
                    : null;
}

运行该示例代码。

String s = this.getABCTernary();
System.out.println( "s: " + s );

C

优点和缺点

  • 三元链的优势在于压缩代码,折叠成单行代码。
  • 不利的一面是,在这种特殊情况下,您调用 getter 方法两次只是为了获得一个值。对于简单的 fetch-the-variable 类型的 getter 来说不是问题,但如果 getter 是一种耗时的方法(例如远程 Web 服务调用),则会影响性能。而且,级联 if-then-else 也有同样的问题,也会调用你的 getter 两次。

性能

这两者在性能方面的比较

Java 中的三元运算符是"short-circuiting",这意味着匹配测试结果的左侧或右侧是唯一调用的代码。在我们这里的代码中,如果getA 返回一个非空值,则立即返回该值。对getBgetC 的进一步调用永远不会执行。所以在这方面,链式三元组的性能与级联 if-then-else 语句相同:first-match 获胜,不再调用。

如果您的意思是执行纳秒级的性能,我不知道。担心会落入过早优化的陷阱。现代 JVM 非常适合优化您的代码。

【讨论】:

    【解决方案4】:

    在我大约 20 年的商业经验之后,我已经形成了一种观点,即追求可读性是绝对愚蠢的,同时,故意编写复杂的代码是邪恶的。

    我知道这完全违背了大众的看法。

    但是,每个人都需要意识到这一点......

    1. 一个人可以阅读的内容不一定可以让下一个人阅读。即使在这个线程中,对于ifOptional 是否更具可读性,我们也有不同的看法。无论我们所处的结构或情况如何,都会发生这类辩论。
    2. 如果我们采用if 选项,它比函数式方法性能更高,每个每次,那么阅读该代码的人就会习惯并找到它更具可读性 - 因为这是他们现在已经习惯的风格。
    3. 高性能代码不一定是“难以阅读”...但这会回到第 1 点和第 2 点。开发人员需要真正了解他们所使用的语言的基础知识并编写适当的代码对于那种语言,而不是试图在他们的代码中形成“英语句子”。

    所以,本质上:使用if...使用Optional

    【讨论】:

      【解决方案5】:

      几天前,我进行了全面的性能分析。有巨大的性能影响。使用 AdoptOpenJDK,if 语句的速度提高了 10 倍。当 JIT 编译器热运行时,这会减少 20% 的损失。

      GraalVM 做得更好:使用冷 JVM 时速度会降低 3 倍,并且在给编译器足够的时间来发挥它的魔力之后,还会有 20% 的性能损失。

      但是,真正的问题是哪个版本更适合阅读和维护应用程序。如果您像我一样,阅读if 语句会更容易,但也有人更喜欢函数式方法。

      如果您准备好深入了解,我邀请您到read my detailed analysis 了解Optional.orElseGet() 及其朋友的性能和实施。

      【讨论】:

        猜你喜欢
        • 2012-01-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-07
        相关资源
        最近更新 更多