之前一篇文章聊过异常排名《Java异常排行榜:哪个异常最常见?》,里面谈到国外一个网站对 Java 异常进行数据分析并排名,结果是 NullPointerException 排第一,本文正好对空指针异常做一个总结,希望对各位同学有所帮助。
在本文中,我展示了一个关于如何处理空指针异常的综合示例。在Java中,null 作为一个特殊值被对象引用,用来表示该对象当前指向的是一块未知内存数据。然而NullPointerException这个异常,则是程序在使用或访问一个对象的引用时,而该对象等于null则被抛出。
那些情况会引发该异常呢?
-
被调用方法的对象为null。
-
访问或修改一个null对象的字段。
-
求一个数组为null对象的长度。
-
访问或修改一个数组为null对象中的某一个值。
-
被抛出的值是null并且是一个Throwable的子类。
-
当你用null对象进行synchronized代码块。
NullPointerException 是 RuntimeException 的子类,因此,Javac 编译器并不会强迫你使用 try-catch 代码块来捕获该异常。
一、为什么需要 null ?
如上所述,null 是 Java 的一个特殊值。它在设计模式方面编码的过程中非常有用,例如空对象模式和单例模式。空对象模式提供了一个对象作为缺少给定类型对象的代理。而单例模式可以确保只创建一个类的实例,主要用于提供一个全局访问的对象。
例如,创建单例类的示例方法是将其所有构造函数声明为private,然后创建一个返回该类的唯一实例的公共方法,如下:
在这个例子中,我们声明了一个 Singleton 类的静态实例。该实例在 getInstance 方法内最多初始化一次。注意本例使用了 null 来确保创建的是唯一实例。
二、如何避免空指针异常
为了避免这种情况 NullPointerException ,请确保在使用运行程序之前,所有对象都已正确初始化。注意,当你声明一个引用变量时,即创建了一个指向对象的指针。在向对象请求方法或字段之前,您必须验证变量「指针」是否为空。
另外,如果引发异常,请使用堆栈中的异常信息进行跟踪。堆栈跟踪是由JVM提供,便于应用程序的调试。找到发生异常的方法和代码行,然后确定哪个引用为null。
在本文的余下部分,我们将介绍一些避免空指针异常的方法。但是,这些方法并非一劳永逸,因此,同学们在编写应用程序时应格外小心。
1、String变量与文本值比较
在编码过程中,String变量与文本值之间的比较是特别常见的。一般被比较的值可以是一个字符串或枚举值。因此,我们不要从空对象调用方法进行比较,而应考虑从文字值中调用方法。如下:
上面的代码片段则会抛出一个NullPointerException。但是,如果我们从文字中调用方法,那么执行流程通常会继续:
为什么呢?去看一下 JDK 源码 java.lang.String 便明白了。
2、检查方法的参数
在执行你自己的方法的主体之前,一定要检查方法传入的参数是否为空。只有在正确检查了参数后,才能继续执行该方法的相应逻辑。否则,您可以抛出一个 IllegalArgumentException 来通知调用方法所传递的参数有问题。
例如:
3、优先使用String.valueOf() 代替toString()
当您代码中的某个对象需要用字符串的方式来表示时,请避免使用该对象的toString方法;因为若你的对象引用为null,则会抛出 NullPointerException。
相反,考虑使用静态String.valueOf方法,该方法不会抛出任何异常,若对象引用为空,则打印「null」字符串。
有的同学可能会问为什么呢?还是那句话读源码 ^_^
下面是基类 Object 的 toString() 方法:
接着,咱们再来看看 String 的 valueOf() 方法做了什么呢?
4、使用三元运算符
该操作是非常有用的,可以帮助我们避免了NullPointerException。格式如下:
上面通过布尔表达式来判断。如果表达式结果为true,则返回value1,否则返回value2。我们可以使用三元运算符来处理空指针,如下所示:
如果str的引用为空,则消息变量将为空。否则,如果str指向实际数据,则该消息将保留它的前10个字符。
这里,一直有个疑惑困扰着我,为什么 Kotlin 这门语言去掉了三元运算符呢?知道的同学欢迎留言,一起来探讨~~~
5、创建返回空集合而不是null值的方法
一个非常好的操作是创建返回一个空集合的方法,而不是一个null值。因为你的代码可以遍历空集合并使用它的方法和字段,而不会抛出一个NullPointerException 。例如:
注意:要熟悉 Collections 这个集合工具类,里面有太多好用的方法了。
6、使用Apache的StringUtils类
Apache的Commons Lang是一个为 java.lang API 提供帮助工具的库,比如字符串操作方法。提供字符串操作的示例类是 StringUtils.java,它对输入的字符串进行了 null 判断。
你可以使用 StringUtils.isNotEmpty, StringUtils.IsEmpty 和 StringUtils.equals 等方法,来避免NullPointerException。例如:
还是老规矩,咱们来读一下相应的源码。
7、习惯用 contains(), containsKey(), containsValue() 方法
如果您的程序在使用集合,请考虑使用contains,containsKey和containsValue方法。例如,从集合中找一个特定键的值:
在上面的代码片段中,我们未检查key是否真的存在于内部Map,因此返回的值可以是 null 。最安全的方法如下:
8、请检查使用的外部方法的返回值是否为 null
在编码中使用外部库是很常见的,这些库可能包含返回引用的方法,需确保返回的值不为 null 。另外,我们要养成在开发的过程中,养成阅读 Javadoc 的习惯,以便更好地理解其功能和返回值。
9、使用断言
断言在测试代码时非常有用,并且可以被使用,以避免 NullPointerException 。Java 断言是用 assert 关键字实现的,并抛出一个 AssertionError 。
请注意,您必须显式启用 JVM 的断言标志,一般在程序启动时,使用 –ea 参数来启用断言。否则,断言将被完全忽略。
使用 Java 断言的示例如下:
如果您执行上面的代码段并传递一个空参数getLength,则会出现以下错误消息:
最后,您可以使用测试框架 JUnit 提供的类 Assert 来使用断言。
10、单元测试
在测试代码的功能和正确性时,单元测试一般非常有用。因此,建议多花一些时间编写一些测试用例,来避免程序出现 NullPointerException。目前,我司的代码覆盖率要达到 95% 以上才能通过。
三、拥有 NullPointerException 的安全方法
1、访问类的静态成员或方法
当你的代码试图访问静态变量或类的方法时,即使对象的引用等于 null,JVM 也不会抛出一个 NullPointerException 。这是由于Java编译器在编译过程中将静态方法和字段存储在方法区或者常量池。因此,静态字段和方法不与对象相关联,而与类的名称相关联。
例如,下面的代码不会抛出NullPointerException:
注意,尽管 SampleClass 等于的实例 null 将会被正确执行。但是,对于静态方法或字段,最好以静态方式访问它们,比如SampleClass.printMessage()。
2、instanceof 操作符
instanceof 即使对象的引用等于 null,也可以使用该运算符。在 instanceof 操作时,参考值等于为null,不抛出 NullPointerException,而是返回 false 。例如,下面的代码片段:
正如预期的那样,执行的结果是:
四