Java 空指针检查以加快代码执行

Java null pointer checking for faster code execution

想象一个方法,该方法将一个对象作为参数并使用 for each 循环进行检查,让我们说一些集合内的其他值,根据传入的参数可以是 found/filtered。在函数开头检查空指针和 return 立即检查空集合或空指针是一个好习惯,还是最好省略空指针检查,因为每个循环都会处理它,但该函数将需要更多时间来执行(因为每次迭代的整体)。并且可以说这个集合并不大(不那么耗时)。

 public ArrayList<Foo> find(Bar bar) {  
        if (bar == null) { // get rid of these part?
            return null;   //
        }                  //  

        ArrayList<Foo> foos = new ArrayList<Foo>();
        for (Foo f: Foo.values()) {
            if (f.someBarCollection.contains(bar)) {
                foos.add(f);
            }
        }

        return foos;
 }

我认为最好立即检查 null 和 return 如果您知道做任何进一步的操作都是浪费时间,因为您知道不需要它们。因此,我以牺牲更短的代码为代价来支持语义,以使事情变得明确。

编辑: 让我进一步详细说明。函数的结果与没有空检查部分的 OR 相同。问题是,我是否应该检查它,只是为了更好的表达(和一点性能提升,但这不是问题),但代码会更长(因为增加了检查)?

这取决于您希望 null 传递给此方法的频率(以及是否有)。

视情况而定

根据您的 API,当接收到的参数具有 null 值时,您可以执行以下操作:

  • 抛出异常。可能 IllegalArgumentException 或描述此错误参数原因的自定义异常。
  • Return 一个 null 值并让客户端在其余代码中处理结果。
  • Return 空结果。如果是 List(不是 ArrayList),你可以 return Collections#emptyList.

无论您使用哪个选项 API/methods,请务必在 javadoc 中正确记录。

是否检查更多的是品味问题而不是性能问题。 但是,为了让您的客户更轻松,您应该不是 return null,而是空集合。

根据用例,您还可以引发异常(如果不允许空值)and/or 用 @NonNull 注释 bar 参数以允许使用可插入检查器.

这取决于预期的 bar 值。如果 nullbar 的允许值,那么您的决定是正确的。否则最好抛出异常。

需要考虑的一件事是面向未来。

如果你能想象集合合法 contains(null) 的时间,那么要么让它 运行 通过(并且 return 一个空集合)或者抛出异常(可能 IllegalArgumentException).抛出异常意味着更改不会影响现有合法代码的行为。

除非你在 bar!=null 时考虑 returning null 'empty'(通常不推荐)它与 return 有点不一致 bar==null.

大多数其他帖子都不鼓励 null 'empty'。 使用它的唯一原因是性能:

//WARNING - THIS CODE IS NOT FIRST CHOICE
public ArrayList<Foo> find(Bar bar) {  
        ArrayList<Foo> foos = null;
        for (Foo f: Foo.values()) {
            if (f.someBarCollection.contains(bar)) {
                if(foos==null){
                    foos=new ArrayList<Foo>();
                }
                foos.add(f);
            }
        }
        return foos;
 }

Collections.emptyList() 是有价值的,但任何认为它可以修改列表的调用代码都会得到 UnsupportedOperationException 的令人讨厌的惊喜。 因此,将 null 替换为 'empty' 可能存在将 NullPointerException 替换为(可能频率较低且更难发现)UnsupportedOperationException 的风险。没有我们想要的那么多进展。

return本地 ArrayList 的下一个选择令人恐惧:

//DANGER - CODING HORROR!

static final sEmpty=new ArrayList<Foo>();

public ArrayList<Foo> find(Bar bar) {  
        ArrayList<Foo> foos = null;
        for (Foo f: Foo.values()) {
            if (f.someBarCollection.contains(bar)) {
                if(foos==null){
                    foos=new ArrayList<Foo>();
                }
                foos.add(f);
            }
        }
        return foos==null?sEmpty:foos;
 }

在这种情况下,无知的代码可能会向假定为空的 ArrayList 添加元素,并导致它们被后续调用 find() 编辑 return。度过一天的好方法就是找到那个!

遗憾的是,我们似乎距离 Java 实现 C++ 风格 const 以区分可变和不可变引用比以往任何时候都远。 所以至少在某些情况下,你会留下 returning 小垃圾对象或 null for 'empty'.

归根结底,Java 库采取的态度是,为了简单起见,创建和销毁大量小对象是可以接受的开销(看看 'boxed' 原语,如 Integer)。许多现代 JVM 都有分代垃圾收集器,旨在与这种风格相协调。

所以如果你还犹豫不决 'accepting' null:

public ArrayList<Foo> find(Bar bar) {  
        if(bar==null){
            throw new IllegalArgumentException("bar==null");
        }
        ArrayList<Foo> foos = new ArrayList<Foo>();
        for (Foo f: Foo.values()) {
            if (f.someBarCollection.contains(bar)) {
                foos.add(f);
            }
        }
        return foos;
 }

否则顺其自然:

public ArrayList<Foo> find(Bar bar) {  
        ArrayList<Foo> foos = new ArrayList<Foo>();
        for (Foo f: Foo.values()) {
            if (f.someBarCollection.contains(bar)) {
                foos.add(f);
            }
        }
        return foos;
 }

仅当此方法已被证明是瓶颈时才考虑替代方案。