Java 默认为空lambda比检查可能为空的lambda好还是坏?

Java 默认为空lambda比检查可能为空的lambda好还是坏?,java,lambda,java-8,Java,Lambda,Java 8,我正在用Java8开发一个小的场景图实现。“基本场景”节点如下所示: public class SceneNode { private final List<SceneNode> children = new ArrayList<>(); protected Runnable preRender; protected Runnable postRender; protected Runnable render; public final void r

我正在用Java8开发一个小的场景图实现。“基本场景”节点如下所示:

public class SceneNode {
  private final List<SceneNode> children = new ArrayList<>();
  protected Runnable preRender;
  protected Runnable postRender;
  protected Runnable render;

  public final void render() {
    preRender.run();
    render.run();
    for (Renderable child : children) {
      child.render();
    }
    postRender.run();
  }
}
  public final void render() {
    if (null != preRender) { preRender.run(); }
    if (null != render) { render.run(); }
    for (Renderable child : children) {
      child.render();
    }
    if (null != postRender) { postRender.run(); }
  }
所以我的问题是,空检查引入的分支的隐式成本是否比JVM最终编译空lambda的成本高或低?看起来检查null的代价应该更高,因为潜在的分支限制了优化,而Java编译器或JVM应该足够聪明,能够将空lambda编译成no-op

默认为空lambda比检查可能为空的lambda好还是坏

这与询问是否最好测试空
字符串
参数或尝试替换空
字符串
基本相同

答案是,这取决于您是否希望将
null
视为编程错误。。。或者不是

我个人的观点是,意外的
null
s应该被视为编程错误,并且应该允许程序与NPE一起崩溃。这样,问题会更早地引起您的注意,并且更容易跟踪和修复。。。如果你替换了一些“makegood”值来阻止NPE被抛出

但当然,这并不适用于预期的
null
值;i、 e.当API javadocs说a
null
是允许的值时,并说明其含义


这还与如何设计API有关。在这种情况下,问题在于您的API规范(即javadoc!)是否应该坚持程序员提供no-op lambda,或者将
null
视为具有相同的含义。这归结为以下两者之间的折衷:

  • API客户端的便利性
  • API实现者工作,以及
  • 稳健性;e、 g.当使用未正确初始化的变量值时

我更关心的是使用空lambda与使用null以及必须执行null检查的运行时性能的影响

我的直觉是,测试
null
会更快,但性能上的任何差异都会很小,而且很可能对应用程序的整体性能影响不大

更新-结果证明我的直觉是“正确的一半”根据@Balder的微观基准测试。对于
-client
模式的JVM,空检查稍微快一点,但还不足以令人担忧。对于
-server
模式的JVM,JIT编译器显然在优化这两种情况,使之成为性能相同的本机代码。)

我建议您处理您会(或至少应该)处理任何潜在的优化问题:

  • 在应用程序运行之前,请推迟任何优化
  • 对应用程序进行基准测试,看看它是否已经足够快了
  • 分析应用程序以了解真正的热点在哪里
  • 开发并测试假定的优化
  • 重新运行基准测试,看看它是否改善了情况
  • 转至步骤2

  • 有趣的是,当JVM使用
    -client
    参数运行时,检查null似乎比调用空lambda或空匿名类快一点。使用
    -server
    运行时,所有方法的性能都相同

    为了测试这一点,我已经用做了一个微基准测试

    以下是测试类(编译所需的最新git格式):

    @VmOptions(“-client”)
    公共类EmptyLambdaTest{
    公共可运行emptyLambda=()->{};
    public Runnable emptyAnonymousType=new Runnable(){
    @凌驾
    public void run(){}
    };
    公共可运行nullAbleRunnable;
    @基准
    公共int TIMEMPTYLAMBDA(int代表){
    int-dummy=0;
    对于(int i=0;i
    以下是基准结果:


    甚至不用麻烦检查
    null
    。相信Java会用空的可运行程序做正确的事情。@LouisWasserman如果你对“正确的事情”的定义是抛出一个NullPointerException,那么是的。我认为他的观点是永远不允许它们为null,因为他明确地指的是空的可运行程序。我必须同意Erwin的观点。Java支持空周期。即使使用可选选项也不检查它是草率的。这是一个完全合法的代码行。可选fubar=null;所以,任何告诉你不需要检查NULL的人都是一个不了解真实世界的白板开发人员。这不是我想要的。当然,对于公共API,返回null和返回空值之间的区别是,或者至少可以是,关于API语义的决定。我在这里问的是不同的。这些字段是内部实现的一部分,在客户端未提供任何操作的情况下,是否填充“preRender”取决于我和我自己。我更关心的是使用空lambda与使用空lambda以及必须执行空检查的运行时性能的影响。如果您要要求使用空lambda,最好还提供以下内容:
    @VmOptions("-client")
    public class EmptyLambdaTest {
    
        public Runnable emptyLambda = () -> {};
        public Runnable emptyAnonymousType = new Runnable() {
            @Override
            public void run() {}
        };
    
        public Runnable nullAbleRunnable;
    
        @Benchmark
        public int timeEmptyLambda(int reps){
            int dummy = 0;
            for (int i = 0; i < reps; i++) {
                emptyLambda.run();
                dummy |= i;
            }
            return dummy;
        }
    
        @Benchmark
        public int timeEmptyAnonymousType(int reps){
            int dummy = 0;
            for (int i = 0; i < reps; i++) {
                emptyAnonymousType.run();
                dummy |= i;
            }
            return dummy;
        }
    
        @Benchmark
        public int timeNullCheck(int reps){
            int dummy = 0;
            for (int i = 0; i < reps; i++) {
                if (nullAbleRunnable != null) {
                    nullAbleRunnable.run();
                }
                dummy |= i;
            }
            return dummy;
        }
    
    }