如何处理空指针以及什么是“空安全”编码方式?

GsM*_*GsM 3 java null

我是一名初级开发人员,我使用java做网站开发。我知道推荐 org.apache.common.lang.StringUtils 因为它的 null 安全性。但是什么是空安全或空安全确切?为什么下面的代码很难看?

if( sth != null ) { ... }

Ye *_*Win 5

这是初学者到中级程序员最常见的问题:他们要么不知道,要么不信任他们参与的合约,并且防御性地过度检查空值。

为什么下面的代码很难看?

 if( sth != null ) { ... }
Run Code Online (Sandbox Code Playgroud)

它并不像你知道的那么难看,但我们认为如果我们null在项目中有很多检查条件,它是额外的检查而不是可读的代码。(对于这种情况,如果您接受 null 是合同中的有效响应;并且...)

但是什么是空安全或空安全确切?
以下是我null根据我经验丰富且最喜欢的作者对“安全”编码方式的建议。

对于收藏对象案例

(1) 返回空数组或集合,而不是空值(Effective Java(参见条款 43)- Joshua Bloch)

// The right way to return an array from a collection
private final List<Cheese> cheesesInStock = ...;

private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];
/**
* @return an array containing all of the cheeses in the shop.
*/
public Cheese[] getCheeses() {
     return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}
Run Code Online (Sandbox Code Playgroud)

以类似的方式,可以使集合值方法在每次需要返回空集合时返回相同的不可变空集合。Collections.emptySetemptyList以及 emptyMapmethods提供您所需要的东西,如下图所示:

// The right way to return a copy of a collection
public List<Cheese> getCheeseList() {
    if (cheesesInStock.isEmpty())
     return Collections.emptyList(); // Always returns same list
   else
     return new ArrayList<Cheese>(cheesesInStock);
}
Run Code Online (Sandbox Code Playgroud)

总之,没有理由nullarray- 或 - collection值方法返回而不是返回空数组或集合。

(2) Don't Return Null - (Clean Code - Uncle Bob)
在很多情况下,特殊情况对象是一个简单的补救方法。想象一下,你有这样的代码:

List<Employee> employees = getEmployees();
if (employees != null) {
  for(Employee e : employees) {
   totalPay += e.getPay();
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,getEmployees可以返回null,但必须返回吗?如果我们改变getEmployeeso它返回一个空列表,我们可以清理代码:

List<Employee> employees = getEmployees();
for(Employee e : employees) {
  totalPay += e.getPay();
}
Run Code Online (Sandbox Code Playgroud)

幸运的是,JavaCollections.emptyList()它返回一个预定义的不可变列表,我们可以使用这一目的:

public List<Employee> getEmployees() {
   if( .. there are no employees .. ) 
     return Collections.emptyList();
}
Run Code Online (Sandbox Code Playgroud)

如果您以这种方式编码,您将最大限度地减少机会NullPointerExceptions并且您的代码将更干净。

不要传递 Null 从方法中
返回null是不好的,但传递null到方法中更糟。除非您使用的API期望您通过null,否则您应该尽可能避免传入null您的代码。

让我们看一个例子来了解原因。这是一种计算两点的度量的简单方法:

public class MetricsCalculator 
{
    public double xProjection(Point p1, Point p2) {
        return (p2.x – p1.x) * 1.5;
    }
…
}
Run Code Online (Sandbox Code Playgroud)

当有人null作为论点通过时会发生什么?

calculator.xProjection(null, new Point(12, 13));
Run Code Online (Sandbox Code Playgroud)

我们NullPointerException当然会得到一个。

我们该如何解决?我们可以创建一个新的异常类型并抛出它:

public class MetricsCalculator 
{
    public double xProjection(Point p1, Point p2) {
        if (p1 == null || p2 == null) {
        throw InvalidArgumentException(
        "Invalid argument for MetricsCalculator.xProjection");
        }
        return (p2.x – p1.x) * 1.5;
    }
}
Run Code Online (Sandbox Code Playgroud)


这是否更好?它可能比 a 好一点nullpointerexception,但请记住,我们必须为 定义一个处理程序InvalidArgumentException。处理者应该怎么做?有什么好的做法吗?

还有另一种选择。我们可以使用一组断言:

public class MetricsCalculator 
    {
        public double xProjection(Point p1, Point p2) {
        assert p1 != null : "p1 should not be null";
        assert p2 != null : "p2 should not be null";
        return (p2.x – p1.x) * 1.5;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是很好的文档,但并不能解决问题。如果有人传递 null,我们仍然会runtime出错。
在大多数编程语言中,没有处理null调用者意外传递的 a 的好方法。因为是这种情况,合理的做法是禁止默认传递 null。当你这样做时,你可以在知道null参数列表中的 a 是一个问题的迹象的情况下进行编码,并且最终会减少粗心的错误。

额外注意:null-return成语很可能从一个故障保持C语言编程,其中阵列长度被从实际阵列单独返回。在C 中,如果返回零作为长度,则分配数组没有任何优势。

对于非收藏对象案例

(1) 使用空对象模式(旧方法)
例如(假设您使用 dao 模式访问数据库)
您需要做的就是返回一个空对象 - 说一个您在DAO 中会有的客户条目,例如...... ..

if (result == null) { return new EmptyUser(); }
Run Code Online (Sandbox Code Playgroud)

其中EmptyUser扩展User并返回适当的条目到 getter 调用,以允许您的其余代码知道它是一个空对象(id = -1 等) 代码示例:

public class User {
    private int id;
    private String name;
    private String gender;
    public String getName() {
       //Code here
    }
    public void setName() {
       //Code here
    }
}

public class EmptyUser extends User {

    public int getId() {
       return -1;
    }

    public String getName() {
       return String.Empty();
   }
}

public User getEntry() {
   User result = db.query("select from users where id = 1");
   if(result == null) {
       return new EmptyUser();
   }
   else {
       return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

(2) 使用 Java 8 Optional
事实上,引入null引用可能是编程语言历史上最严重的错误之一,即使它的创建者Tony Hoare称其为十亿美元的错误。

以下是null根据新Java版本的最佳替代方案:

2.1. Java 8以上

Java 8你可以使用java.util.Optional 开始。

这是一个如何在非空返回情况下使用它的示例:

public Optional<MyEntity> findMyEntity() {
    MyEntity entity = // some query here
    return Optional.ofNullable(entity);
}
Run Code Online (Sandbox Code Playgroud)


2.2. 之前Java 8

Java 8您可以使用来自 Google Guava 的com.google.common.base.Optional之前。

这是一个如何在非空返回情况下使用它的示例:

public Optional<MyEntity> findMyEntity() {
    MyEntity entity = // some query here
    return Optional.fromNullable(entity);
}
Run Code Online (Sandbox Code Playgroud)



空对象模式 Vs 的注意事项Java 8 Optional

我绝对更喜欢Optional它,generic并且它已被甲骨文和谷歌采用,这两家世界上最大的 IT 公司给予了很多荣誉。

我什至会说Null Object Pattern在 中没有任何意义Java,它已经过时了Optional如果你检查一下 Java 9 中的新功能,你会看到Oracle使之Optional更进一步,阅读这篇文章文章

  • *总而言之,没有理由从“数组”或“集合”值方法返回“null”而不是返回空数组或集合。*我认为这过于笼统。Null 通常意味着该方法没有结果。无结果和空结果之间存在语义差异。在适当的情况下使用空结果显然是更好的选择,但并不总是合适的。对于空字符串也是如此。 (2认同)