关于java:如何处理空指针以及什么是“null safe”代码方式?

How to handle null pointer and what is “null safe” way to code?

本问题已经有最佳答案,请猛点这里访问。

我是初级开发人员,我用Java做网站开发。我知道建议使用org.apache.common.lang.stringutils,因为它的安全性为空。但是什么是空安全或空安全正确性?为什么下面的代码很难看?

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


I know org.apache.common.lang.StringUtils is recommended because of its null safety.

对于最基本的字符串操作,通常使用字符串值,代码中会充满if (foo != null) {...

Apache编写了大量Java代码,因此它已经竭尽全力为公共操作提供帮助实用程序。

例如:

1
2
3
4
int i = s.indexOf('foo');    // Can throw NullPointerException!
String b = s.toUpperCase();  // Can throw NullPointerException!!
if (s.equals('bar')          // Can throw NullPointerException!!!
   // ...

如果你开始阅读StringUtils类,你会发现null被反复处理——indexOfupperCaseequals——都涉及null的安全。这可以大大简化代码。

来自apache.org:

"StringUtils handles null input Strings quietly. That is to say that a null input will return null. Where a boolean or int is being returned details vary by method"

根据我的经验,使用StringUtils的决定直接受到您在项目中使用的if (foo == null) { ...语句的数量以及您的代码中的依赖项是否已经导入了这个特定的实用程序/库的影响,所有这些都可能在项目的生命周期中发生变化。


对于初学者到中级程序员来说,这是最常见的问题:他们要么不知道,要么不信任他们正在参与的契约,并且防御性地检查空值。好的。

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

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

这并不像你所知道的那么难看,但我们认为如果在项目中有大量的null检查条件,这是额外的检查和不可读的代码。(在这种情况下,如果你接受了其中无效是合同条款中的有效回应;以及…)好的。

但是什么是空安全或空安全正确性?< BR>下面是我根据我的经验丰富和最喜欢的作者建议的"null安全"编码方式。好的。集合对象大小写

(1)返回空数组或集合,而不是空值(有效Java(参见项目43)- Joshua Bloch)好的。

1
2
3
4
5
6
7
8
9
10
// 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);
}

以类似的方式,可以使集合值方法返回相同的每次需要返回空集合时都是不可变的空集合。Collections.emptySetemptyListemptyMapmethods提供了您所需要的,如下所示:
好的。

1
2
3
4
5
6
7
// 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);
}

总之,没有理由从array返回null—或collection值方法,而不是返回空数组或集合。好的。

(2)不返回空值-(清除代码-Bob叔叔)
在许多情况下,特殊情况对象是一种简单的补救方法。假设您有这样的代码:好的。

1
2
3
4
5
6
List<Employee> employees = getEmployees();
if (employees != null) {
  for(Employee e : employees) {
   totalPay += e.getPay();
  }
}

现在,getEmployees可以返回null,但是必须这样做吗?如果我们更改getEmployeeso,使其返回空列表,我们可以清除代码:好的。

1
2
3
4
List<Employee> employees = getEmployees();
for(Employee e : employees) {
  totalPay += e.getPay();
}

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

1
2
3
4
public List<Employee> getEmployees() {
   if( .. there are no employees .. )
     return Collections.emptyList();
}

如果您这样编码,您将最小化NullPointerExceptions的机会,并且您的代码将更干净。好的。

不要传递空值< BR>从方法返回null是不好的,但将null传递给方法更糟。除非您使用的是希望通过null的API,否则应尽量避免在代码中传递null

让我们看一个例子来了解原因。下面是一个计算两点公制的简单方法:
好的。

1
2
3
4
5
6
7
public class MetricsCalculator
{
    public double xProjection(Point p1, Point p2) {
        return (p2.x – p1.x) * 1.5;
    }

}

当有人把null作为论点时会发生什么?< BR>好的。

1
calculator.xProjection(null, new Point(12, 13));

当然,我们会得到一辆NullPointerException。好的。

我们怎么修?我们可以创建一个新的异常类型并抛出它:
好的。

1
2
3
4
5
6
7
8
9
10
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;
    }
}

< BR>这样更好吗?它可能比NullPointerException要好一点,但是记住,我们必须为InvalidArgumentException定义一个处理程序。处理程序应该做什么?有什么好的行动方案吗?

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

1
2
3
4
5
6
7
8
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;
    }
}

这是很好的文档,但不能解决问题。如果有人通过NULL,我们仍然会有一个runtime错误。< BR>在大多数编程语言中,没有好的方法来处理null,也就是说偶然路过一个呼叫者。因为是这样,理性的方法是默认情况下禁止传递空值。当你这样做的时候,你就可以知道参数列表中的null是一个问题的迹象,并且以更少的粗心错误结束。好的。

附加说明:null返回习惯用法可能是C编程语言的遗留问题,在C编程语言中,数组长度与实际数组分开返回。在C语言中,如果返回零作为长度,那么分配数组没有任何好处。好的。对于非集合对象大小写

(1)使用空对象模式(旧方法)
例如(假设您使用DAO模式访问数据库)
您需要做的只是返回一个空对象-比如说您的DAO中的一个客户条目,比如……好的。

1
if (result == null) { return new EmptyUser(); }

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;
    }
}

(2)使用Java 8可选的
事实上,引入null引用可能是编程语言历史上最严重的错误之一,甚至它的创建者托尼·霍尔(TonyHoare)也把它称为他数十亿美元的错误。好的。

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

2.1。Java 8及以上好的。

Java 8开始,您可以使用java.util.optional。好的。

下面是一个示例,说明如何在非空返回案例中使用它:好的。

1
2
3
4
public Optional<MyEntity> findMyEntity() {
    MyEntity entity = // some query here
    return Optional.ofNullable(entity);
}

< BR>2.2。在Java 8之前好的。

Java 8之前,您可以使用google guava中的com.google.common.base.optional。好的。

下面是一个示例,说明如何在非空返回案例中使用它:好的。

1
2
3
4
public Optional<MyEntity> findMyEntity() {
    MyEntity entity = // some query here
    return Optional.fromNullable(entity);
}

< BR>好的。

空对象模式对Java 8 Optional的注:好的。

我肯定更喜欢Optional,它比generic要多得多,而且它已经被甲骨文和谷歌采用,这两家公司是世界上最大的IT公司之一,为它提供了大量的信贷。
BR/>我甚至会说,在EDCOX1 8中,空对象模式不再有任何意义,它过时了,EDOCX1,14是未来。如果你稍微检查一下Java 9中的新内容,你会看到Oracle使EDCOX1 14更进一步,读这篇文章。好的。好啊.


空指针可以说是最常见的运行时崩溃源——它们实际上是定时炸弹。Java中可怕的null检查被大多数高级开发人员认为是代码的气味,通常是设计糟糕的标志。

更安全的方法是使用一个如zhc所提到的空对象模式。在这种情况下,每当您声明一个未初始化的对象时,都会创建它,而不是让它作为一个空对象一直保持到填充为止。

一个过于简单的例子就是总是用"来实例化一个字符串。空标签(在我看来)比撞车好。


带有空检查的代码并不难看。例如,如果您使用C语言或GO语言检查某些代码,它们将充满空检查。还有,关于你的"零安全"方式。我认为这是某种使用GOF中的空对象模式的方法。