Bakumon

switch enum 导致的空指针异常

今天遇到了一个不太常见的空指针异常,特此记录下来。

NullPointerException

异常如下:

Fatal Exception: java.lang.NullPointerException
Attempt to invoke virtual method 'int java.lang.Enum.ordinal()' on a null object reference

当看到这个异常时,第一反应是 ordinal() 是什么方法?我没调用过啊???

冷静下来通过报错位置的提示,发现异常出现在 switch(enum) 语句上。我们用以下示例代码说明:

// 定义枚举类
private enum Position { 
  TOP, BOTTOM
}

// 声明 Position 变量,用于记录控制类型
private Position mPosition;

// foo 方法中对控制类型进行判断
public void foo() {
  switch(mPosition) {
    case TOP:
      print("TOP");
      break;
    case BOTTOM:
      print("BOTTOM");
      break;
    default:
      print("不是 TOP 也不是 BOTTOM");
      break;
  }
}

异常原因分析

以上代码首先定义了一个表示 位置类型 的枚举类 Position,它有两个类型: TOP 和 BOTTOM。接着声明一个成员变量 mPosition,用来记录位置类型,每当位置改变时,就更新它的值。最后在一个方法里利用 switch 语句判断出位置类型的不同来执行不同的逻辑。

当我们还没有给 mPosition 赋值时,希望执行 foo() 方法打印 不是 TOP 也不是 BOTTOM,但实际上当执行到 switch(mPosition) 就报出了上面所说的空指针异常。

这是为什么呢?原来 switch 语句判断的对象是不能为 null 的,这时候 mPosition 还没有被赋值,是空的,所以就出现了空指针异常。

但是为什么空指针会发生在调用 ordinal() 方法时?

我们知道早期的 switch 语句只支持基本类型,从 Java 1.5 才开始支持 String 类型,但是还没有支持其他引用类型。枚举其实是一种特殊的类,也就是引用类型,我们却可以把枚举用在 switch 语句中,这显然是矛盾的。

这个矛盾的根源来自于 JVM。JVM 为了让 switch 语句支持枚举,当发现 switch 语句判断的是枚举时,会调用枚举对象的 ordinal() 方法,这个方法返回 int 值(表示顺序),用 int 来判断,这就回到了基本类型。

所以我们看到了空指针异常发生在 int java.lang.Enum.ordinal() 调用时。

如何避免

在使用 switch 语句判断 String 或 enum 时,一定要保证值不能为空,上面的示例代码中,我们为了保证 mPosition 不为空,可以在声明时就初始化值,如果初始值是 TOP 或 BOTTOM 都不合适的话,可以加一种枚举类型(如 NONE)作初始值,同时也表示没有位置类型。

private enum Position { 
  TOP, BOTTOM, NONE
}
// 初始化赋值
private Position mPosition = NONE;

这样就保证初始时不为 null 了,并且后续赋值时,也要避免赋空值。