章
目
录
Java中的NullPointerException(NPE)是一种未经检查的异常,它继承自RuntimeException。NullPointerException不强制我们使用try-catch块来处理它。
对于大多数Java开发人员来说,NullPointerException通常是一个噩梦。它通常在我们最不希望出现的时候冒出来。
我也花了很多时间寻找原因和处理空值问题的最佳方法。我将在这里写一些行业惯例,分享一些专家的建议以及我多年来的学习经验。
1.为什么会在代码中出现NullPointerException?
NullPointerException是一种运行时条件,我们试图访问或修改一个尚未初始化的对象。这实际上意味着对象的引用变量没有指向任何地方,引用的是空值(’null’)。
在给定的示例中,String s已经声明但没有初始化。当我们尝试在下一条语句中访问它时s.toString(),就会出现NullPointerException。
public class SampleNPE
{
public static void main(String[] args)
{
String s = null;
System.out.println( s.toString() ); // 's' 未初始化,即为null
}
}
2.NPE经常发生的常见地方
好吧,NullPointerException可以因各种原因在代码的任何地方发生,但根据我的经验,我已经准备了一个最常见地方的列表。
- 调用未初始化的对象上的方法
- 在方法中传递的参数为空
- 在空对象上调用toString()方法
- 在if块中比较对象属性时没有检查空等式
- 针对依赖注入的框架(如Spring)进行不正确的配置
- 在空对象上使用synchronized
- 链式语句,即在单个语句中进行多个方法调用
这不是一个详尽无遗的列表。还有其他几个地方和原因。如果您能回忆起其他任何地方,请留下评论,这将帮助其他人。
3.避免NullPointerException的最佳方法
3.1. 使用三元运算符
三元运算符会在不为null时返回左侧的值,否则会计算右侧的值。它的语法如下:
boolean expression ? value1 : value2;
如果表达式为true,整个表达式将返回value1,否则返回value2。
它更像是一个if-else结构,但更有效和表达力强。为了防止NullPointerException(NPE),可以像下面的代码一样使用这个运算符:
String str = (param == null) ? "NA" : param;
3.2. 使用Apache Commons StringUtils进行字符串操作
Apache Commons Lang是一组用于各种操作的实用工具类的集合,其中之一是StringUtils.java。
在您的代码中使用以下方法来更好地处理字符串。
- StringUtils.isNotEmpty()
- StringUtils. IsEmpty()
- StringUtils.equals()
if (StringUtils.isNotEmpty(obj.getvalue())){
String s = obj.getvalue();
....
}
3.3. 使用”快速失败”的方法参数验证
我们应该始终在方法的开始处进行方法输入验证,以便代码的其余部分不必处理不正确输入的可能性。
因此,如果有人将null作为方法参数传递,问题将在执行生命周期的早期而不是在某个更深层次的位置中提前出现,而在后者中,根本问题将更难以识别。
在大多数情况下,采用”快速失败”的行为是一个不错的选择。
3.4. 考虑使用基本数据类型而不是对象
空值问题出现在对象引用指向空的情况下。因此,使用基本数据类型总是安全的。根据需要考虑使用基本数据类型,因为它们不会出现空引用问题。
所有基本数据类型都有一些默认值与之关联,所以请小心使用。
3.5. 慎重考虑链式方法调用
虽然链式语句在代码中看起来不错,但它们对于防止NPE并不友好。
一条单独的语句分布在多行上将使您在堆栈跟踪中得到第一行的行号,而不管它出现在何处。
ref.method1().method2().method3().methods4();
这种类型的链式语句将仅打印“在行号xyz发生了NullPointerException”。这真的很难调试这样的代码。如果可能的话,尽量避免使用这样的调用。
3.6. 使用valueOf()替代toString()
如果我们必须打印任何对象的字符串表示形式,那么考虑不使用toString()方法。这是NPE的一个很容易受到攻击的点。
相反,使用String.valueOf(object)。即使在这种情况下对象为null,也不会引发异常,并将“null”打印到输出流中。
3.7. 避免从方法中返回null
避免NPE的一个绝妙的方法是返回空字符串或空集合,而不是null。在这里,Java 8的Optionals是一个很好的选择。
在整个应用程序中保持一致地这样做。如果这样做,您将注意到大量的null检查变得不再需要。
List<string> data = null;
@SuppressWarnings("unchecked")
public List getDataDemo()
{
if(data == null)
return Collections.EMPTY_LIST; //返回不可修改的list
return data;
}
上述方法的用户,即使他们忽略了空值检查,也不会看到难看的NPE。
3.8. 不鼓励将null传递为方法参数
我见过一些方法声明,这些方法期望传递两个或更多参数。即使一个参数被传递为null,方法也会以不同的方式工作。要避免这种情况。
相反,我们应该定义两个方法:一个带有单个参数,另一个带有两个参数。
使参数传递成为强制性的。这在编写方法内的应用程序逻辑时非常有帮助,因为您可以确保方法参数不会为null;因此,就不需要写不必要的if判断和断言。
3.9. 在“安全”的非空字符串上调用equals()
而不是编写下面的字符串比较代码:
if (param.equals("check me")) {
// some code
}
我们可以优化如下:
if ("check me".equals(param)) {
// some code
}
4.空指针异常(NullPointerException)安全的操作
4.1. instanceof 运算符
instanceof 运算符是空指针异常(NPE)安全的。因此,instanceof null 始终返回 false。
这个运算符不会引发空指针异常。如果您记住了这个事实,就可以消除混乱的条件代码。
// 不必要的代码
if (data != null && data instanceof InterestingData) {
}
// 更少更简单的代码
if (data instanceof InterestingData) {
}
4.2. 访问类的静态成员
如果您正在处理静态变量或静态方法,即使引用变量指向null,您也不会得到空指针异常,因为静态变量和方法调用是在编译时基于类名绑定的,与对象无关。
MyObject obj = null;
//没有空指针异常,因为staticAttribute是在类MyObject中定义的静态变量。
String attrib = obj.staticAttribute;
请告诉我,如果您知道一些不会在遇到null时失败的编程语言结构,那将非常好。
5.如果必须在某些地方允许NullPointerException
《Effective Java》中的Joshua Bloch说:“可以说,所有错误的方法调用归结为非法参数或非法状态,但是对于某些类型的非法参数和状态,通常使用其他异常。如果调用者在某个不允许使用null值的参数上传递了null,惯例规定应抛出NullPointerException而不是IllegalArgumentException。”
因此,如果必须在代码的某些地方允许NullPointerException,那么请确保使它们比通常情况下更具信息性。
请看下面的示例:
package com.howtodoinjava.demo.npe;
public class SampleNPE {
public static void main(String[] args) {
// call one method at a time
doSomething(null);
doSomethingElse(null);
}
private static String doSomething(final String param) {
System.out.println(param.toString());
return "I am done !!";
}
private static String doSomethingElse(final String param) {
if (param == null) {
throw new NullPointerException(
" :: Parameter 'param' was null inside method 'doSomething'.");
}
System.out.println(param.toString());
return "I am done !!";
}
}
两个方法调用都输出如下:
Exception in thread "main" java.lang.NullPointerException
at com.howtodoinjava.demo.npe.SampleNPE.doSomething(SampleNPE.java:14)
at com.howtodoinjava.demo.npe.SampleNPE.main(SampleNPE.java:8)
Exception in thread "main" java.lang.NullPointerException: :: Parameter 'param' was null inside method 'doSomething'.
at com.howtodoinjava.demo.npe.SampleNPE.doSomethingElse(SampleNPE.java:21)
at com.howtodoinjava.demo.npe.SampleNPE.main(SampleNPE.java:8)
显然,第二个堆栈跟踪更具信息性,有助于简化调试。将来请使用这种方式。
我已经分享了关于NullPointerException的经验。如果你知道与这个主题相关的其他内容,请与我们所有人分享!