什么是 NullPointerException(空指针)

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容