可选vs if / else-if性能Java 8

时间:2019-05-21 09:28:58

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

你好,我有两个代码示例

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+,那么性能损失会是多少?

预先感谢

6 个答案:

答案 0 :(得分:14)

请勿将Optional用作条件逻辑。

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

仅仅因为您可以将它们很好地链接成一行并不意味着它是可以理解的。您实际上也一无所获。性能开销可能很大。最坏的情况是先创建N对象,然后将其丢弃。只需呆在您的“普通” if-else链上即可。


与其寻找让您的当前代码更具可读性的方法,不如退一步,问问自己为什么,您需要15-20条if-else语句。你能分解一些逻辑吗?为什么首先需要为具有多个可能具有不同类型的多个不同字段使用一个吸气剂?等等

答案 1 :(得分:5)

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

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

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

答案 2 :(得分:3)

tl; dr

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

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

三元链接

正如Answer by Lino正确指出的那样,您正在尝试使Optional超出其原始设计目的(在lambda和stream中返回值)。通常最好仅将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方法,只是为了获得一个值。对于简单的可变获取类型的getter来说不是问题,但是如果getter是一种费时的方法(例如远程Web服务调用),则会影响性能。而且,级联的if-then-else也有同样的问题,还两次调用您的getter。

性能

  

这两个在性能方面的比较

Java中的三元运算符是"short-circuiting",这意味着与测试结果匹配的左侧或右侧是唯一调用的代码。在此处的代码中,如果getA返回一个非null值,则立即返回该值。从未执行对getBgetC的进一步调用。因此,就此而言,链式三元组的性能与级联的if-then-else语句相同:先匹配胜出,无需进一步调用。

如果您将性能表示为执行的纳秒级,我不知道。担心这一点将落入过早优化的陷阱。现代JVM在优化代码方面经过了很好的调整。

答案 3 :(得分:2)

几天前,我进行了彻底的性能分析。这会对性能产生巨大影响。使用AdoptOpenJDK,if语句的速度提高了10倍。当JIT编译器热运行时,这将减少20%的罚款。

GraalVM做得更好:冷的JVM的速度降低了3倍,并且在给编译器足够的时间执行其魔术之后,还会有20%的性能损失。

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

如果您准备进行深入的探讨,我邀请您read my detailed analysis来介绍Optional.orElseGet()及其朋友的性能和实现。

答案 4 :(得分:1)

在经过20年的商业经验之后,我认为追求可读性是绝对的愚蠢,同时故意编写复杂的代码是邪恶的。

我知道这完全违背了民意。

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

  1. 一个人可读的内容不一定对下一个可读。即使在此线程中,对于ifOptional是否更具可读性,我们也有不同的看法。无论我们所处的构造或情况如何,都会发生这类辩论。
  2. 如果我们采用if选项,该选项比功能方法的性能更高,每一次 ,那么阅读该代码的人就会得到习惯了并找到 MORE READABLE -因为这是他们现在已经习惯的样式。
  3. 性能代码不一定非要“难于阅读” ...但这会循环回到第1点和第2点。对于开发人员来说,实际上要知道他们使用的语言的基础并编写适当的代码是一个问题。而不是尝试在其代码中形成“英语句子”。

因此,从本质上讲:与if一起使用...不要不要使用该Optional

答案 5 :(得分:0)

我同意不过度使用Optional的方法,它们变得混乱不堪,就像嵌套流在多维数组上进行迭代一样。

但是,出于好奇,我创建了以下应用程序来计算Optional超过If/Else的性能命中率。它始终使我的机器运行时间少于10毫秒/百万。如果您想使用它,可以使用以下代码,并可以将其用于类似的比较:

import java.util.Optional;
import java.util.concurrent.Callable;

public class OptionalVsIfElse {

    public static long time(double numberOfRuns, Callable<Object> tested) throws Exception {
        long start = System.currentTimeMillis();
        for (long idx = 0; idx < numberOfRuns; idx++) 
            tested.call();

        return System.currentTimeMillis() - start;
    }

    public static void main(String[] args) throws Exception {
        double numberOfRuns = 10000000;

        Object target = new Object();
        Object comparison = new Object();

        long ifTime = time(numberOfRuns, () -> {
            return (target != null) ? target : comparison; 
        });

        long optionalTime = time(numberOfRuns, () -> {
            return Optional.ofNullable(target).orElse(comparison);
        });

        double performanceHitRatio = (double)(optionalTime - ifTime) * 1_000_000 / numberOfRuns;
        System.out.println(String.format("Performance Hit Ratio: %.2f ms / million runs", performanceHitRatio));
    }
}