Java中的让人痛恨的NullPointException

1.null何错之有

对于Java程序员来说,null是令人头痛的问题,Java程序员时常会遇到空指针(NullPointException)的问题。相信很多人都特别害怕程序中出现NullPointException,因为这种异常往往伴随着代码的非预期运行

在编程语言中,空引用(Null Reference)是一个与空指针类似的概念,空引用是一个已宣告但其并未引用到一个有效对象的变量

在Java1 中旧包含了空引用和NullPointException了,但空引用是伟大的计算机科学家Tony Hoare早在1965年发明的,最初作为编程语言ALOGOL W的一部分
1965年,英国一位名为Tony Hoare的计算机科学家在设计ALOGOL W语言时提出了空引用的想法,ALGOL W是第一批在堆上分配记录的类型语言之一。Hoare选择空引用这种方式,"只是因为这种方式实现起来非常容易".虽然他的设计初衷就是要"通过编译器的自动检测机制,确保所有使用引用的地方都是绝对安全的",他还是决定为空引用开个绿灯,因为他认为这是为“不存在的值”建模最容易方式

但是在2009年,他开始为自己曾经做过这样的决定而后悔不已,把空引用称为"一个价值十亿美元的错误".实际上,Hoare的这段话低估了过去五十年来数百万程序员为修复空引用所耗费的代价。因为在ALGOL W之后出现的大多数现代程序设计语言,包括Java,都采用了同样的设计方式,其原因是为了与更老的语言保持兼容,或者就像Hoare曾经陈述的那样,“只是这种方法实现起来比较容易”

相信大家都对null和NullPointException深恶痛绝,因为它确实会带来各种各样的问题,如

  • 它是错误之源,NullPointException是目前Java程序开发总最经点的异常。它会使代码膨胀
  • 它让代码充斥着深度嵌套的null检查,代码的可读性糟糕透顶
  • 它自身是毫无意义的,null自身没有任何的语义,尤其它代表的是在静态类型语言中以一种错误的方式对缺失变量值的建模
  • 它破坏了Java的哲学。Java一直试图避免让开发人员意识到指针的存在,唯一的例外就是null指针
  • 它是在Java的类型系统上开了个口子——null并不属于任何类型,这意味着它可以被赋值给任意引用类型的变量,这回导致问题——当这个变量被传递到系统中的另一部分后,你将无法获知这个null变量最初赋值 到底是什么类型

2.其他语言如何解决NullPointException问题

除了Java还有其他的面向对象语言,在其他的一些语言中,是如何解决NPE问题的?
在Groovy中使用安全导航操作符(Safe Navigation Operator)可以访问可能为null的变量:

def carInsuranceName = person?.car?.insurance?.name

Groovy的安全导航操作符能够避免你在访问这些可能为空引用的变量时发生NPE,在调用链中的变量遭遇null时将空引用沿着调用链传递下去,返回一个null,另外,在Haskell和Scale也有类似的替代品,如Haskell的Maybe类型、Scala中的Option[T]

在Kotlin中,其类型系统严格区分一个引用可以容纳null还是不能容纳,也就是说,一个变量是否可空必须显式声明,对于可空变量,在访问其成员变量时必须做空处理,否则无法编译通过

var a: String ="abc"
a =null // 编译错误

如果允许为空,则可以声明一个可空字符串,写作String?:

var b: String ? ="abc" // String?表示该String类型变量可为空
b = null // 编译通过

3.Java进行了这么多的迭代,对NPE做了哪些事情呢?

其实在Java8推出之前,Google的Guava库中旧率先提供了Optional接口来使null快速失败。于是Java8吸收了其优点,Optional在可能为null的对象上做了一层封装,Optional对象包含了一些方法来显式地处理某个值是否存在还是缺失,Optional类强制开发人员思考值不存在地情况,这样就能避免潜在地空指针异常

但设计Optional类的目的并不是完全取代null,它的目的使设计更易理解的API.通过Optional开发人员可以从方法签名就知道这个方法有可能返回一个缺失的值,这样会强制让我们处理

在JDK14中,新增了一个值得一提的JEP358:Helpful NullPointException,它的思想是,既然无法避免,那就让它对开发人员更有帮助一些,每个Javer都遇到过NPE异常,由于NPE可以发生在程序的几乎任何地方,因此试图捕获并从异常中恢复通常是不切实际的。因此,开发人员通常依赖于JVM来确定NPE实际发生时的来源,例如,假设在这段代码中出现了一个NPE

a.i=99;

JVM将打印出导致异常的NPE的方法、文件名、行号

Exception in thread "main" java.lang.NullPointException
at Prog.main(Prog.java:5)

通过以上堆栈信息,可以定位到a.i=99这一行,并推断出a一定是null
但是对于更复杂的代码,如果不使用调试器,则不可能确定哪个变量是null

a.b.c.i=99;

根本无法确定到底是哪个变量出现了null值,在JDK14z中,当在运行期试图对一个null对象进行引用时,JVM依然会抛出一个NPE,除此之外,还会通过分析程序的字节码指令,精确地确定哪个变量时null,并且在对战信息中明确提示出来,例如在a.i=99发生NPE,将会打印如下堆栈

Exception in thread "main" java.lang.NullPointException:Cannot assign field "i" because "a" is null
at Prog.main(Prog.java:5)

如果时a.b.c.i=99中出现了null导致NPE,则会打印以下堆栈信息

Exception in thread "main" java.lang.NullPointEexception:Cannot read field "c" because "a.b" is null
at Prog.main(Prog.java:5)

堆栈中明确指出了到底时哪个对象为null而导致了NPE,一旦发生NPE开发者可以通过堆栈信息第一时间确定是哪个对象的问题

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coffee_babe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值