感觉 NullPointerException (空指针异常)一直对我阴魂不散,大部分 bug 都是这个引起的,有时候我在想到底什么是 NullPointerException 呢,哪又该如何预防 NullPointerException ?
之前在看技术号文章( Stack Overflow),有人提问:什么是 NullPointerException,没想到这个问题还挺火的。
一、抛砖引玉 —— 指针概念
C / C++ 中的指针的,是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量。
可以把指针理解为地址。
而在 Java 中是没有指针这个概念,人们称它问为引用(对象引用)。它需要指向一个实例对象(通过new方法构造)的。
二、来龙去脉 —— what is NullPointerException
NullPointerException (空指针异常),因为引用没有指向具体的实例,所以当访问这个引用的方法的时候就会产生这种异常。
好比一个风筝(对象),手里的线(指针)牵引着它,突然线断了,风筝飞走了,这就是所谓的空指针异常。
翻译过来:你找不到对象,因为你对象丢了。
String str = "测试字符串";
System.out.println(str.length());
//上面的代码没有问题,但是如果改成下面的代码:
String str ;//初始化为 null
System.out.println(str.length());//就会产生NullPointerException异常了
三、极往知来 —— 哪些地方可能产生 NullPointerException
那么问题来了,哪些地方有出现 NPE 的可能,打仗就得知道“敌人”是谁,它在哪。
当返回类型为基本数据类型, return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
数据库的查询结果可能为 null。
集合里的元素即使 isNotEmpty ,取出的元素也可能为 null 。
远程调用返回对象时,一律要求进行空指针判断,以防止 NPE。
对于 Session 中获取的数据,建议进行 NPE 检查,以避免空指针。
级联调用 obj.getA().getB().getC(); 的一连串调用,容易产生 NPE 。
PS:可以使用 JDK8 的Optional 类来防止出现NPE 问题。
四、未雨绸缪 —— 预防 NullPointerException
防止 NPE 问题是一个程序员的基本素养。
NPE 问题多了有点烦,于是疯狂的用 if 条件判断,虽然是暴力解决但代码乱七八糟的,一点都不美观。
public void doWork(String id){
if(StringUtils.isNotEmpty(id)){
//to do
}
Object object = find.Object(id);
if(object != null){
//to do
}
}
看了一些资料后才知道有些地方需要处理空指针,有些地方则不需要。
1)对于传入的参数,方法非底层方法,那么只按照自己认定的方式处理。比如你调用我的方法 A 并且传入参数,我要求这个参数不能为null,如果为null 就会抛出 NullpointerException,然后只需要按照不为 null 的方式处理,不会判断是否为 null;
- 对于传入的参数,方法是底层的方法,传入参数有可能为 null ,那就需要该方法进行相应空指针判断了,我就需要抛出对应的异常信息;
- 如果是调用其他方法得到的结果,如果一定需要不为 null 的,那么就需要判空,因为你同样需要抛出对应的异常信息;
五、兵来将挡 —— 寻找并处理 NullPointerException
意外总会发生,当 NullPointerException 发生了,别慌,可以根据堆栈信息,找到错误。
比如以这个 demo 为例
public class Main {
public static void main(String[] args) {
doWork(null);//故意传 null
}
public static void doWork(String str){
System.out.println(str.length());
}
}
报错信息
从图片分析,错误发生在 “at …” 列表处,第一个“at 处”就是错误最初发生的位置。
带有一系列异常的示例
有时,应用程序会捕获异常并将其重新引发为另一个异常的原因。通常看起来像:
34 public void getBookIds(int id) {
35 try {
36 book.getId(id); // this method it throws a NullPointerException on line 22
37 } catch (NullPointerException e) {
38 throw new IllegalStateException("A book has a null property", e)
39 }
40 }
这可能会给您一个堆栈跟踪,如下所示:
Exception in thread "main" java.lang.IllegalStateException: A book has a null property
at com.example.myproject.Author.getBookIds(Author.java:38)
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
Caused by: java.lang.NullPointerException
at com.example.myproject.Book.getId(Book.java:22)
at com.example.myproject.Author.getBookIds(Author.java:36)
... 1 more
与此不同的是“Caused by”。有时,例外会有多个“Caused by” 部分。对于这些,您通常希望找到“根本原因”,这将是堆栈跟踪中最低的 “ Cause by” 部分之一。在我们的例子中是:
Caused by: java.lang.NullPointerException <-- root cause
at com.example.myproject.Book.getId(Book.java:22) <-- important line
同样,对于此例外,我们会想看看行22的Book.java,看看有什么可能导致NullPointerException这里。
库代码更令人生畏的示例
通常,堆栈跟踪要比上面的两个示例复杂得多。这是一个示例(虽然很长,但是展示了多个级别的链接异常):
javax.servlet.ServletException: Something bad happened
.....
Caused by: com.example.myproject.MyProjectServletException
at com.example.myproject.MyServlet.doPost(MyServlet.java:169)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
....
Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2822)
at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:268)
.....
Caused by: java.sql.SQLException: Violation of unique constraint MY_ENTITY_UK_1: duplicate value(s) for column(s) MY_COLUMN in statement [...]
at org.hsqldb.jdbc.Util.throwError(Unknown Source)
at org.hsqldb.jdbc.jdbcPreparedStatement.executeUpdate(Unknown Source)
... 54 more
在这个例子中,还有更多。我们最关心的是从代码中寻找方法,这些方法可能是com.example.myproject 程序包中的任何方法。在上面的第二个示例中,我们首先要查找根本原因,即:
Caused by: java.sql.SQLException
但是,该方法下的所有方法调用都是库代码。因此,我们将移至其上方的“ Caused by”,并寻找源于我们代码的第一个方法调用,即:
at com.example.myproject.MyEntityService.save(MyEntityService.java:59)
就像在前面的例子中,我们应该看看MyEntityService.java就行59,因为这是此错误的起源(这个有点明显出了什么问题,因为SQLException中发生的错误,但调试程序是什么,我们以后是)。
参考文献:《阿里巴巴 Java 开发手册》
https://www.cnblogs.com/liaochong/p/code.html
https://blog.csdn.net/JavaEETeacher/article/details/4285488
https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it
https://stackoverflow.com/questions/3988788/what-is-a-stack-trace-and-how-can-i-use-it-to-debug-my-application-errors