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("default");
      break;
  }
}

异常原因分析

我们定义了一个表示位置类型的枚举类,它有两个类型: TOP(上) 和 BOTTOM(下)。同时我们用一个成员变量记录位置类型,每当位置改变时,都更新这个成员变量的值。最后在一个方法里通过 switch 语句判断位置类型,根据不同值执行不同的逻辑代码。

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

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

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

我们知道早期的switch只支持基本类型,从 Java 1.5 才开始支持 String 类型,但还不支持其他引用类型。枚举其实也是 class,我们却可以把枚举用在 switch 语句中。这个矛盾的根源来自于 JVM,JVM 发现 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;

未完待续。。。