【问题标题】:Replace null check with Optional用 Optional 替换空检查
【发布时间】:2021-05-15 17:03:52
【问题描述】:

由于我是 java 14 的粉丝,我想知道是否用 Optional.ofNullable 替换空检查 对这种语言是安全的。我知道一个简单的空检查在新创建新对象(如可选成本)时不会消耗任何内存,但我猜它对性能或内存影响为零。谁能启发我?

我的游戏代码是这样的:

if (item!= null)
{
   if (item.getCrystal() == Crystal.A)
   {
     player.getInventory().addItem(inventoryItem);
   }
}

我喜欢并且觉得很酷的东西

Optional.ofNullable(item).filter(i -> i.getCrystal() == Crystal.A).ifPresent(k -> player.getInventory.addItem(i));

有人能告诉我我没问题吗?也许很酷但要花很多钱?我不知道。 非常感谢。

【问题讨论】:

  • 有些人觉得这是滥用,有些人(比如我)喜欢它。阅读这样的代码对我来说要容易得多,但这当然是主观的。
  • 我绝对喜欢你的第二种方法。可选当然可以使代码更易于阅读。不过需要考虑一些事情:1) 我会拆分行,所以每个方法都在自己的行上(就像每行都以. 开头)。这样,您就不会得到无法阅读的超长单行字。 2) 您可能会考虑将item 设置为 Optional 更进一步,因此当它遇到这部分代码时它已经是 Optional 了。 (这可能取决于您的代码结构) 3)我可能会放弃 filter() 并使用传统的 if 语句来清楚起见,纯粹是个人喜好。
  • 回答您的实际问题,是的,将字段包装到对象中会产生一些内存和性能开销。但效果相当微乎其微。您应该为清晰而编程,如果性能成为问题,请从那里开始工作。对于现代计算机,内存几乎从来都不是问题,我什至不会花脑细胞时间去思考它(内存泄漏除外)。
  • 正如我对其他人所说,我们为 2.500 名玩家说话,这可能会在几分钟内发生 2.500 次。这会影响我的表现吗?
  • 传统观念是,在 Java 中,Optional<T> 类型应该用作方法返回类型,以向客户端用户强烈指示该方法可能无法返回值,并且因此,他应该检查是否是这样。 Optional 的其他用途很可能是代码异味,但也许还有一些其他合法的用例。我同意您使用 Optional 来检查 null 返回值实际上比显式 null 检查可读性差。

标签: java


【解决方案1】:

在这种简单的情况下,即时编译器可能会忽略对象分配,或者至少将其分配在堆栈上,因此开销可以忽略不计。

这是一个小基准:

public abstract class Benchmark {

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name + "\t" + time() + " ns / iteration";
    }

    private BigDecimal time() {
        try {
            // automatically detect a reasonable iteration count (and trigger just in time compilation of the code under test)
            int iterations;
            long duration = 0;
            for (iterations = 1; iterations < 1_000_000_000 && duration < 1_000_000_000; iterations *= 2) {
                long start = System.nanoTime();
                run(iterations);
                duration = System.nanoTime() - start;
                cleanup();
            }
            return new BigDecimal((duration) * 1000 / iterations).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Executes the code under test.
     * @param iterations
     *            number of iterations to perform
     * @return any value that requires the entire code to be executed (to
     *         prevent dead code elimination by the just in time compiler)
     * @throws Throwable
     *             if the test could not complete successfully
     */
    protected abstract Object run(int iterations) throws Throwable;
    
    /**
     * Cleans up after a run, setting the stage for the next.
     */
    protected void cleanup() {
        // do nothing
    }

    public static void main(String[] args) throws Exception {
        Integer[] a = {null, -1, null, 1}; // mix nulls and real values
        
        System.out.println(new Benchmark("Optional") {
            @Override
            protected Object run(int iterations) throws Throwable {
                int[] sum = {0};
                for (int i = 0; i < iterations; i++) {
                    Optional.ofNullable(a[i & 3]).filter(k -> k > 0).ifPresent(k -> sum[0] += k);
                }
                return sum[0]; 
            }
        });
        System.out.println(new Benchmark("if != null") {
            @Override
            protected Object run(int iterations) throws Throwable {
                int[] sum = {0};
                for (int i = 0; i < iterations; i++) {
                    var k = a[i & 3];
                    if (k != null && k % 2 != 0) {
                        sum[0] += k; 
                    }
                }
                return sum[0]; 
            }
        });
    }
}

这表明使用 Optional 的开销约为 1 ns,即现代 CPU 每秒可以构造和评估大约 10 亿个 Optional 对象。在除了最极端和人为的工作负载之外,使用Optional 不会对性能产生足够的影响,以至于人类可以注意到。

因此,使用Optional 的决定不应以性能考虑为指导,而应以哪个版本更清晰、更简单地表达自己为指导。

在这种情况下,我认为 if 语句实际上更具可读性。当然,您已将所有内容压缩到一行,但您可以使用 if 语句来做同样的事情:

if (item != null && item.getCrystal() == Crystal.A) player.getInventory().addItem(inventoryItem);

如果你这样做,你会注意到 if 语句实际上比你的版本更短更切题:

Optional.ofNullable(item).filter(i -> i.getCrystal() == Crystal.A).ifPresent(k -> player.getInventory.addItem(i));

当然,在实际代码中,您可能希望行保持合理的短,所以比较是

if (item != null && item.getCrystal() == Crystal.A) {
    player.getInventory().addItem(inventoryItem);
}

Optional.ofNullable(item).filter(i -> i.getCrystal() == Crystal.A)
    .ifPresent(k -> player.getInventory.addItem(i));

再次,我发现第一个版本更具可读性,因为它包含的单词更少。

【讨论】:

  • 我只是喜欢这样的编码,但是对于每个玩家都会发生这种情况的游戏,而我们为 2.500 名玩家说话,这种情况可能会在几分钟内发生 2.500 次。这会影响我的表现吗?还是 Java 和现代机器在 2020 年不在乎?
  • 可能会影响性能。创建额外的对象(Optional)不是免费的。我已经在微基准测试中对其进行了测量,速度变化非常明显。然而,在实际代码中,速度的变化可能可以忽略不计。唯一确定的方法是测量实际代码中的实际性能,规则是“过早的优化是万恶之源”。
  • 我刚刚做了一个测试,null 是 0.006 纳秒,而 Optional 是 0.7 纳秒到 1.5 毫秒(如果没有初始化我猜?)
  • @TheLegendBeginsTremors:我的猜测是差异并没有那么大。你说的是 2 个数量级,这是值得怀疑的。更有可能是您没有正确使用微基准测试工具。
  • @TheLegendBeginsTremors:1.5 毫秒超过一百万个 cpu 周期。 Optional 的构建和评估可能需要这么长时间,这完全令人难以置信。我的测试表明大约 1 ns,根据值为 null 的频率而略有差异。
猜你喜欢
  • 2022-01-16
  • 2017-08-09
  • 1970-01-01
  • 1970-01-01
  • 2017-03-21
  • 1970-01-01
  • 2015-07-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多