美文网首页
什么是 NullPointerException(空指针)

什么是 NullPointerException(空指针)

作者: ProudLin | 来源:发表于2019-11-26 11:22 被阅读0次

感觉 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;

  1. 对于传入的参数,方法是底层的方法,传入参数有可能为 null ,那就需要该方法进行相应空指针判断了,我就需要抛出对应的异常信息;
  1. 如果是调用其他方法得到的结果,如果一定需要不为 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());
    }
}

报错信息


image.png

从图片分析,错误发生在 “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

相关文章

网友评论

      本文标题:什么是 NullPointerException(空指针)

      本文链接:https://www.haomeiwen.com/subject/puaswctx.html