IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    scala雾中风景(23): Nothing类型引发的NullPointerException

    hongjiang发表于 2015-05-29 17:37:22
    love 0

    这个问题以前遇到过,这次又发生了,记录一下,避免新人再犯类似错误。

    一个DAO的方法调用 mybatis 里的 SqlSessionTemplate.selectOne("...") 方法时没有指定类型,大致代码如下:

    def check(...): Boolean = {
        ...
        val data = moneySessionTemplate.selectOne("queryXXX", params)
        data != null
    }
    

    程序意图是判断数据库里是否有某条记录,存在则返回返回true。 运行的时候上面的代码可能会抛出 NullPointerException 并提示是在 data != null 这一行,让人乍一看感觉很诡异。

    这里又是类型系统的一个”陷阱”,因为val data 是一个不存在的值,它是Nothing类型。为何会是Nothing类型,又是因为selectOne方法的泛型参数在运行期类型无法推断所致的。我们模拟一下:

    ➜  cat -n Test.scala
     1
     2  object Test {
     3
     4    def selectOne[T](): T = { null.asInstanceOf[T] }
     5
     6    def main(args: Array[String]) {
     7      val r = selectOne()
     8      println("ok?")
     9    }
    10  } 
    

    上面的代码,编译和执行都没有问题,但当我们增加一行判断r是否为空的语句时:

    ➜  cat -n Test.scala
     1
     2  object Test {
     3
     4    def selectOne[T](): T = { null.asInstanceOf[T] }
     5
     6    def main(args: Array[String]) {
     7      val r = selectOne()
     8      if ( r != null )  // 运行时异常
     9        println("ok?")
    10    }
    11  }
    

    运行时在上面的第8行,会抛出空指针异常:

    ➜  scala Test
    java.lang.NullPointerException
        at Test$.main(Test.scala:8)
        at Test.main(Test.scala)
        ...
    

    究其原因是因为r在之前被推导为了Nothing类型,是没有对应任何实例的一个“幽灵”,在访问这种类型的变量时都会抛出NullPointerException。那么问题来了,r的类型是由selectOne方法决定的,在这个方法里我明明是把null造型成结果类型返回的,为啥这里r的类型不是Null或AnyRef而是Nothing呢?

    因为调用selectOne方法的时候没有显示的声明类型参数T,编译器会对这种情况采用Nothing作为类型参数,比如:

    scala> val l = new java.util.ArrayList
            l: java.util.ArrayList[Nothing] = []
    

    所以 val r = selectOne() 这条语句实际被翻译为了(通过-Xprint:jvm)

    val r: Nothing = selectOne().asInstanceOf[Nothing]
    

    selectOne()的运行期结果并不是null,而是一个Nothing类型的幽灵,因为没有任何其他类型的值可以在运行期显式造型为Nothing类型,除了它自己:

    null.asInstanceOf[Nothing] // 编译通过,运行时抛NPE
    
    "test".asInstanceOf[Nothing]  // 编译通过,运行时抛ClassCastException
    

    因为调用方法时类型参数的缺失,在类型推导时致使val r成了一个“幽灵”: 不可访问的值;后续对它的访问产生了NPE.



沪ICP备19023445号-2号
友情链接