第十周_R-What is a NullPointerException, and how do I fix it?

79 阅读3分钟

做了很长时间开发了,记得最开始写代码的时候,就是最容易出现空指针的,只是把代码写完,完全不考虑判断校验问题,也是因为刚接触生产开发,还没有这些意识。不过后面开发就逐渐完备了,下意识的会关注这些东西。

原文链接:

stackoverflow.com/questions/2…

问题

  • 什么是空指针(java.lang.NullPointerException),什么导致的?
  • 可以使用什么方法/工具来确定原因,以防止异常导致程序过早终止?

回答

第一个问题

Java 中的类型

Java 中有两种类型变量:

  1. 基本类型:包含数据,可以直接操作变量。通常来说基本类型都是小写字母开头的。比如:int / char

Java 中有8 个基本类型变量:

byte/short/int/float/long/double/boolean/char/

  1. 引用类型:变量拥有内存地址,变量指向这个内存地址。如果你想引用他,需要使用 . 来访问一个属性或方法,或者使用 [ 来索引一个数组。通常都是大写字母开头的,比如 Object 。

概念

思考下面的代码:你申明了 int 类型的变量,但是没有初始化

int x;
int y = x + x;

上面这两行代码会导致程序崩溃,因为我们尝试使用没有初始化的基本数据类型。

所有基本数据类型被使用前,都必须初始化。

有趣的是:引用类型可以被设置 null :意味着,我啥也没引用。你可以得到一个 null 值:你可以明确的设置或者当没有初始化的时候,编译器不会捕捉它(Java 会自动设置一个 null)

当你想要使用引用对象的时候,如果这个引用被明确的设置 null 或者 Java 自动赋值 null。你就会得到一个 NullPointerException。

NPE 发生在你使用变量的时候,还没有为这个变量创建对象。

示例

Integer num;
num = new Integer(10);

第一行代码申明了一个 num 变量,但是没有初始化对象,此时 Java 会自动设置一个值 null.

第二行代码,new 关键字实例化了一个对象,并将其指向 num .

有时候,自己定义的变量,使用的时候可能会提示你 “num 还没有初始化”。但是更多时候,比如调用返回值,封装他人的接口。就会直接 NPE 了。

比如:

Integer num;
//下面两行都会提示你需要初始化 
System.out.println(num);
num.toString();
num = new Integer(10);
public void doSomething(SomeObject obj) {
   // Do something to obj, assumes obj is not null
   obj.myMethod();
}
//这样的就直接 NPE
doSomething(null);

除了方法的逻辑会抛出 NPE 外,还可以使用检查方法/工具类明确抛出 NPE.

Objects.requireNonNull(obj, "obj must not be null");

提前校验的好处是:

  1. 可以返回你定制的错误,定位问题更加快速/准确
  2. 对于方法的其余部分,你可以知道这个 obj 一定不是 null。可以安全的引用

在某些情况下,方法的空参数也是可以被接受的,那么此时需要检查下并另外做逻辑代码。

比如:

public void doSomething(SomeObject obj) {
   // Do something to obj, assumes obj is not null
   if(obj == null){
    // do something
   }else{
    obj.myMethod();
   }
}

第二个问题

Finally, How to pinpoint the exception & cause using Stack Trace

在 Java14 中,NPE 能被溯源,比如下面的报错信息:

in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.size()" because "list" is null

导致发生 NPE 的列表

  • 访问一个空引用的实例字段(静态字段不算)
  • 调用一个空引用的方法(静态方法不算)
  • 直接抛出 NPE
  • 访问一个空数组的元素
  • 对 null 进行同步 synchronized(obj){}
  • 任何整数/浮点数运算符都能抛出 NPE,如果它的一个操作数是一个框定的空引用的话
  • 使用 for 循环一个空列表或数组
  • switch(foo){},如果 foo 是null
  • name1::name2或primaryExpression::name形式的方法引用在name1或primaryExpression评估为null时抛出一个NullPointerException。JLS的注释说,someInstance.someStaticMethod()不会抛出NPE,因为someStaticMethod是静态的,但是someInstance::someStaticMethod还是会抛出NPE!