原文: https://howtodoinjava.com/java/exception-handling/how-to-effectively-handle-nullpointerexception-in-java/

Java NullPointerException是非受检的异常,并且扩展了RuntimeExceptionNullPointerException不会强迫我们使用catch块来处理它。 对于大多数 Java 开发人员社区而言,此异常非常像一场噩梦。 当我们最不期望它们时,它们通常会弹出。

在寻找解决此类问题的原因和最佳方法时,我也花费了大量宝贵的时间。 我将在这里编写一些最佳实践,这些最佳实践遵循了行业惯例,一些专家讲座以及我自己的经验。

  1. Table of Contents
  2. 1\. Why NullPointerException occur in the code
  3. 2\. Common places where NullPointerException usually occur
  4. 3\. Best ways to avoid NullPointerException
  5. 4\. Available NullPointerException safe operations
  6. 5\. What if you must allow NullPointerException in some places

1. 为什么在代码中出现NullPointerException

NullPointerException是代码中您尝试访问/修改尚未初始化的对象的情况。 从本质上讲,这意味着对象引用变量未指向任何位置,并且未引用任何内容或引用了null。 一个抛出空指针异常的示例 Java 程序。

  1. package com.howtodoinjava.demo.npe;
  2. public class SampleNPE
  3. {
  4. public static void main(String[] args)
  5. {
  6. String s = null;
  7. System.out.println( s.toString() ); // 's' is un-initialized and is null
  8. }
  9. }

2. Java NullPointerException通常发生的常见位置

好吧,由于各种原因,NullPointerException可能会出现在代码中的任何位置,但我根据自己的经验准备了最常用的地点清单。

  1. 在未初始化的对象上调用方法
  2. 方法中传递的参数为null
  3. null的对象上调用toString()方法
  4. 在不检查null相等性的情况下比较if块中的对象属性
  5. 对于像 Spring 这样的依赖注入的框架来说,配置不正确
  6. null的对象上使用synchronized
  7. 链接语句,即单个语句中的多个方法调用

这不是详尽的清单。 还有其他几个地方和原因。 如果您还可以回忆起其他任何内容,请发表评论。 它也会帮助其他人(初学者)。

3. 避免 Java NullPointerException的最佳方法

3.1 三元运算符

如果运算符不为null,则结果为左侧的值,否则求值右侧。 它具有如下语法:

  1. boolean expression ? value1 : value2;

如果expression计算为true,则整个表达式将返回value1,否则返回value2。 它更像if-else构造,但是更有效和更具表现力。 为了防止NullPointerException(NPE),请像下面的代码一样使用此运算符:

  1. String str = (param == null) ? "NA" : param;

3.2 使用 Apache Commons StringUtils进行字符串操作

Apache Commons lang 是几个工具类的集合,用于各种操作之王。 其中之一是StringUtils.java。 使用StringUtils.isNotEmpty()验证作为参数传递的字符串是否为空或空字符串。 如果不为null或为空; 然后进一步使用它。

其他类似方法是StringUtils.IsEmpty()StringUtils.equals()。他们在自己的 javadocs 中声称,如果StringUtils.isNotBlank()抛出 NPE,则 API 中存在错误。

  1. if (StringUtils.isNotEmpty(obj.getvalue())){
  2. String s = obj.getvalue();
  3. ....
  4. }

3.3 尽早检查方法参数是否为空

您应该始终将输入验证置于方法的开头,以使其余代码不必处理输入错误的可能性。 因此,如果有人传递空值,那么事情将在栈的早期破裂,而不是在更难以识别根本问题的更深的位置破裂。

在大多数情况下,针对快速失败行为的选择是一个不错的选择。

3.4 考虑原始类型而不是对象

当对象引用指向无内容时,将发生空问题。 因此,尽可能多地使用原始类型始终是安全的,因为它们不会受到空引用的影响。 所有原始类型都必须附加一些默认值,因此请当心。

3.5 仔细考虑链接方法调用

虽然链式语句很容易在代码中查看,但它们不是 NPE 友好的。 一条分散在多行中的语句将为您提供栈跟踪中第一行的行号,无论它出现在何处。

  1. ref.method1().method2().method3().methods4();

这些链接的语句将仅打印“NullPointerException在行号xyz中发生”。确实很难调试此类代码。 避免这样的调用。

3.6 使用String.valueOf()而不是toString()

如果您必须打印任何对象的字符串表示形式,请不要使用object.toString()。 这是 NPE 的非常软的目标。 而是使用String.valueOf(object)
即使object在第二种方法中为null,它也不会给出异常,并且将输出“null”到输出流。

3.7 避免从方法中返回null

避免 NPE 的一个很棒的技巧是返回空字符串或空集合,而不是返回null。 在您的应用程序中一致地执行此操作。 您会注意到,如果这样做,则不需要进行大量的空检查。

例如:

  1. List<string> data = null;
  2. @SuppressWarnings("unchecked")
  3. public List getDataDemo()
  4. {
  5. if(data == null)
  6. return Collections.EMPTY_LIST; //Returns unmodifiable list
  7. return data;
  8. }

使用上述方法的用户即使错过了空检查,也不会看到难看的 NPE。

3.8 不鼓励传递空参数

我已经看到一些方法声明,其中方法需要两个或多个参数。 如果参数之一作为null传递,则方法以某种不同的方式起作用。 避免这种情况。

相反,您应该定义两种方法; 一个带有单个参数,第二个带有两个参数。 强制传递参数。 当您在方法内部编写应用程序逻辑时,这很有帮助,因为您可以确保方法参数不会为null。 因此您不会提出不必要的假设和主张。

3.9 在“安全”的非空字符串上调用String.equals(String)

代替在下面的代码中编写的字符串比较

  1. public class SampleNPE {
  2. public void demoEqualData(String param) {
  3. if (param.equals("check me")) {
  4. // some code
  5. }
  6. }
  7. }

像这样写上面的代码。 即使参数作为null传递,这也不会引起 NPE。

  1. public class SampleNPE {
  2. public void demoEqualData(String param) {
  3. if ("check me".equals(param)) // Do like this
  4. {
  5. // some code
  6. }
  7. }
  8. }

4. 可用的NullPointerException安全操作

4.1 实例运算符

instanceof运算符是 NPE 安全的。 因此,instanceof null总是返回false。 它不会导致NullPointerException。 如果您记住这一事实,则可以消除混乱的条件代码。

  1. // Unnecessary code
  2. if (data != null &amp;&amp; data instanceof InterestingData) {
  3. }
  4. // Less code. Better!!
  5. if (data instanceof InterestingData) {
  6. }

4.2 访问类的静态成员

如果您要处理静态变量或静态方法,则即使您的引用变量指向null,也不会获得null指针异常,因为静态变量和方法调用是在编译时根据类名绑定的,并且与对象无关

  1. MyObject obj = null;
  2. String attrib = obj.staticAttribute; //no NullPointerException because staticAttribute is static variable defined in class MyObject

如果您知道更多这样的语言构造,当遇到null时也不会失败,请告诉我。

5. 如果必须在某些地方允许NullPointerException怎么办

《Effective Java》中的 Joshua bloch 说:“可以说,所有错误的方法调用都可以归结为非法参数或非法状态,但是其他异常通常用于某些类型的非法参数和状态。 如果调用者在某个参数中传递了null,该参数禁止使用null值,则约定将抛出NullPointerException而不是IllegalArgumentException。”

因此,如果必须在代码中的某些位置允许NullPointerException,则请确保使其具有更多信息,然后通常是这样。 看下面的例子:

  1. package com.howtodoinjava.demo.npe;
  2. public class SampleNPE {
  3. public static void main(String[] args) {
  4. // call one method at a time
  5. doSomething(null);
  6. doSomethingElse(null);
  7. }
  8. private static String doSomething(final String param) {
  9. System.out.println(param.toString());
  10. return "I am done !!";
  11. }
  12. private static String doSomethingElse(final String param) {
  13. if (param == null) {
  14. throw new NullPointerException(
  15. " :: Parameter 'param' was null inside method 'doSomething'.");
  16. }
  17. System.out.println(param.toString());
  18. return "I am done !!";
  19. }
  20. }

这两个方法调用的输出是这样的:

  1. Exception in thread "main" java.lang.NullPointerException
  2. at com.howtodoinjava.demo.npe.SampleNPE.doSomething(SampleNPE.java:14)
  3. at com.howtodoinjava.demo.npe.SampleNPE.main(SampleNPE.java:8)
  4. Exception in thread "main" java.lang.NullPointerException: :: Parameter 'param' was null inside method 'doSomething'.
  5. at com.howtodoinjava.demo.npe.SampleNPE.doSomethingElse(SampleNPE.java:21)
  6. at com.howtodoinjava.demo.npe.SampleNPE.main(SampleNPE.java:8)

显然,第二个栈跟踪更有用,并且使调试容易。 将来使用。

到目前为止,我对NullPointerException的了解已完成。 如果您知道有关该主题的其他观点,请与我们所有人分享!

学习愉快!