Stack Overflow 上最火的一个问题:什么是 NullPointerException

在逛 Stack Overflow 的时候,发现最火的问题竟然是:什么是 NullPointerException(java.lang.NullPointerException),它是由什么原因导致的,有没有好的方法或者工具可以追踪它发生的原因?

真没想到,这个问题浏览的次数多达 250 万次!所以,我想是时候把最高赞的回答整理一下分享出来了。请随我来。

声明引用变量(即对象)时,实际上是创建了一个指向对象的指针。请看以下代码:

int x;
x = 10;

第一行代码声明了一个名为 x 的变量(int 类型),Java 会把它初始化为 0。第二行代码把 x 赋值为 10,意味着 10 将被写入到 x 所指向的内存位置上。

但是呢,当我们尝试声明一个引用类型时,情况将会有所不同。

Integer num;
num = new Integer(10);

第一行代码声明了一个名为 num 的变量(Integer 类型),Java 把它初始化为 null,表示“什么都没有指向 ”。

第二行代码中,new 关键字创建了一个 Integer 类型的对象,并将变量 num 指向该对象。

当我们声明了一个变量,却没有将该变量指向任何创建的对象,然后就使用它的时候,NullPointerException 就发生了。大多数情况下,编译器会发现这个问题,并且提醒我们“xxxx may not have been initialized”。

假如有这样一段代码:

public void doSomething(SomeObject obj) {
   //do something to obj
}

在这种情况下,我们没有创建对象 obj,而是假设它在 doSomething() 方法被调用之前就创建了。

现在假设在此之前它没有创建。我们这样调用 doSomething() 方法:

doSomething(null);

这就意味着 doSomething() 方法的参数 obj 为 null。如果该方法还要使用 obj 继续做点什么,最好提前抛出 NullPointerException,因为开发者需要该信息来进行调试。

还有另外一种替代方法,判断 obj 是不是 null,如果是,就小心行事,做某些不会引起 NullPointerException 的事情;如果不是,就放心大胆地做该做的事情。

/**
  * @param obj An optional foo for ____. May be null, in which case 
  *  the result will be ____.
  */
public void doSomething(SomeObject obj) {
    if(obj != null) {
       //do something
    } else {
       //do something else
    }
}

那假如程序真的出现了 NullPointerException,该怎么追踪堆栈信息,找到错误的根源呢?

简单来说,堆栈信息是应用程序在引发 Exception 时调用的方法列表,可以准确地定位到错误发生的根源。就像下面这样。

Exception in thread "main" java.lang.NullPointerException
        at com.example.myproject.Book.getTitle(Book.java:16)
        at com.example.myproject.Author.getBookTitles(Author.java:25)
        at com.example.myproject.Bootstrap.main(Bootstrap.java:14)

就上面这个堆栈信息来说,错误发生在“at …”列表处,第一个“at 处”就是错误最初发生的位置。

at com.example.myproject.Book.getTitle(Book.java:16)

为了调试,我们可以打开 Book.java 类的第 16 行,它可能是:

15   public String getTitle() {
16      System.out.println(title.toString());
17      return title;
18   }

从这段代码中可以看得出,错误的原因很可能是因为 title 为 null。

有时候,应用程序会捕获一个异常,然后把它作为另外一种类型的异常抛出。就像下面这样:

34   public void getBookIds(int id) {
35      try {
36         book.getId(id);    // 这里可能会引发 NullPointerException
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”——它就是堆栈信息中最下面的那个。

Caused by: java.lang.NullPointerException <-- 根本原因
        at com.example.myproject.Book.getId(Book.java:22) 

同样,我们需要查看一下 Book.java 的第 22 行,找到可能引发 NullPointerException 的原因。

有时候,堆栈信息要比上面的例子凌乱得多。参考下面这个。

javax.servlet.ServletException: Something bad happened
    at com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:60)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.example.myproject.ExceptionHandlerFilter.doFilter(ExceptionHandlerFilter.java:28)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
    at com.example.myproject.OutputBufferFilter.doFilter(OutputBufferFilter.java:33)
    at org.mortbay.jetty.Server.handle(Server.java:326)
    at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
    at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
    at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
    at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
    at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
    at org.mortbay.jetty.bio.SocketConnector$Connection.run(SocketConnector.java:228)
    at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
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)
    at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1166)
    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)
    at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:321)
    at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:344)
    at $Proxy19.save(Unknown Source)
    at com.example.myproject.MyEntityService.save(MyEntityService.java:59) <-- relevant call (see notes below)
    at com.example.myproject.MyServlet.doPost(MyServlet.java:164)
    ... 32 more
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)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:57)
    ... 54 more

这个例子当中的堆栈信息实在是太多了,令人眼花缭乱。如果按照之前提供的方法(堆栈信息中最下面的那个)找最深层次的那个“cause”,它就是:

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)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
    at org.hibernate.id.insert.AbstractSelec

但其实它并不是的,因为抛出这个异常的方法调用者属于类库代码(c3p0 类库),所以我们需要往上找异常发生的原因,并且这个异常很可能是由我们自己编写的代码(com.example.myproject 包下)引发的,于是我们找到了这样一段异常信息。

at com.example.myproject.MyEntityService.save(MyEntityService.java:59)

顺藤摸瓜,看看 MyEntityService.java 的第 59 行,它就是引发错误的根本原因。

Image placeholder
chensheng
未设置
  41人点赞

没有讨论,发表一下自己的看法吧

推荐文章
Stack Overflow 上 370万浏览量的一个问题:如何比较 Java 的字符串?

在逛StackOverflow的时候,发现了一些访问量像喜马拉雅山一样高的问题,比如说这个:如何比较Java的字符串?访问量足足有370万+,这不得了啊!说明有很多很多的程序员被这个问题困扰过。PS:

浏览器中的JavaScript:什么是文档对象模型?什么是DOM操作?

JavaScript并没有那么糟糕。作为运行在浏览器中的脚本语言,它对于网页操作非常有用。在本文中,我们将看到可以用哪些手段来修改HTML文档和交互。什么是文档对象模型?文档对象模型是在浏览器中一切的

Linux/Unix 基础:什么是 Linux?

简单来讲,Linux是一个操作系统(OS)。我们都很熟悉其他操作系统,就像Microsoftwindows,AppleMacOS,iOS,Googleandroid,等等这些,linux就像它们一样,

Kubernetes 基础信息:什么是 Kubernetes?

简介 Kubernetes(常简称为K8s,在希腊语意为“舵手”或“驾驶员”)是用于自动部署、扩展和管理容器化(containerized)应用程序的开源系统。 由JoeBeda、BrendanBur

Stack Overflow上188万浏览量的提问:Java 到底是值传递还是引用传递?

在逛StackOverflow的时候,发现了一些访问量像阿尔卑斯山一样高的问题,比如说这个:Java到底是值传递还是引用传递?访问量足足有188万+,这不得了啊!说明有很多很多的程序员被这个问题困扰过

基础信息:什么是 MySQL?

MySQL是一个开源的深受欢迎的关系型数据库管理系统(简称RDBMS)。目前排名第二,仅次于Oracle数据库。 MySQL可以免费下载,但是,还提供了几个付费版本,这些版本提供了附加功能。 顾名思义

面试题:请解释一下什么是虚拟内存?

内存对于用户来说就是一个字节数组,我们可以根据地址来访问到某个字节或者某些字节:很久之前的内存很久很久之前,一台机器上只放置一个程序,操作系统仅仅作为一个函数库存在。对于内存来说,除去操作系统的代码和

10分钟,用TensorFlow.js库,训练一个没有感情的“剪刀石头布”识别器

大数据文摘出品编译:Luciana、小七、宁静“剪刀石头布”是我们小时候经常玩的游戏,日常生活中做一些纠结的决策,有时候也常常使用这种规则得出最后的选择,我们人眼能很轻松地认知这些手势,“石头”呈握拳

TensorFlow技术主管Peter Wardan:机器学习的未来是小而美

大数据文摘授权转载自OReillyAIPeteWardan任谷歌TensorFlow移动和嵌入式团队的leader,在O’ReillyAIConference2019的Keynote演讲环节,他对机器

2019机器学习框架之争:与Tensorflow竞争白热化,进击的PyTorch赢在哪里?

大数据文摘出品来源:thegradient编译:张大笔茹、曹培信、刘俊寰、牛婉扬、Andy2019年,机器学习框架之争进入了新阶段:PyTorch与TensorFlow成为最后两大玩家,PyTorch

入门 | Tensorflow实战讲解神经网络搭建详细过程

作者| AI小昕编辑| 磐石出品| 磐创AI技术团队【磐创AI导读】:本文详细介绍了神经网络在实战过程中的构建与调节方式。之前我们讲了神经网络的起源、单层神经网络、多层神经网络的搭建过程、搭建时要注意

TensorFlow与PyTorch之争,哪个框架最适合深度学习

谷歌的Tensorflow与Facebook的PyTorch一直是颇受社区欢迎的两种深度学习框架。那么究竟哪种框架最适宜自己手边的深度学习项目呢?本文作者从这两种框架各自的功能效果、优缺点以及安装、版

如何使用TensorFlow机器学习对图像进行分类?

本文将介绍如何使用迁移学习使用TensorFlow机器学习平台对图像进行分类。在机器学习环境中,迁移学习是一种技术,使我们能够重用已经训练的模型并将其用于另一个任务。图像分类是将图像作为输入并为其分配

TensorFlow 2.0 代码实战专栏开篇

作者|  AymericDamien编辑 | 奇予纪出品| 磐创AI团队原项目|  https://github.com/aymericdamien/TensorFlow-Examples/ 写在前面

PostgreSQL DBA(31) – Backup&Recovery#4(搭建流复制)

PostgreSQL通过流复制StreamingReplication可轻松实现高可用HA环境的搭建.本节简单介绍了搭建流复制环境的基本步骤.Step1主库:创建用户 创建复制用户replicator

基于JS的高性能Flutter动态化框架MXFlutter

导语:18年10月份,手机QQ看点团队尝试使用Flutter,做为iOS开发,一接触到Flutter就马上感受到,Flutter虽然强大,但不能像RN一样动态化是阻碍我们使用她的唯一障碍了。看Goog

面试题:如何理解 Linux 的零拷贝技术?

本文讲解Linux的零拷贝技术,云计算是一门很庞大的技术学科,融合了很多技术,Linux算是比较基础的技术,所以,学好Linux对于云计算的学习会有比较大的帮助。本文借鉴并总结了几种比较常见的Linu

同一字段多个查询条件时遇到的一个问题

需求,加载礼物表中,租户id=0和租户id=10的数据,并排除id=10,11,12第一次写法(这个写法是错误的)$whereOr=[ ['tenant_id','=',0], ['gift_id',

关于面试题:[1, 2, 3].map(parseInt)问题的剖析

一、前言最近有小伙伴在公号中咨询了胡哥这道面试题,窃以为是比较有意思的一道面试题,于此分享给各位小伙伴。先把答案给了各位,和你理解的一样吗?![1,2,3].map(parseInt)//[1,NaN

历史上最著名计算机病毒,似乎都成了我们的回忆

Windows勒索病毒似乎让全球计算机用户都闻风丧胆,不过这其实真的不算什么。然而令人始料不及的是,即便勒索病毒传遍了100多个国家,也仅仅才收获了5万美金。所以说勒索病毒真的不算啥。历史上比勒索病毒

史上最全Oracle数据泵常用命令

导读:expdp和impdp是oracle数据库之间移动数据的工具,本文简单总结了数据泵的常用命令,希望对大家有帮助。前言expdp和impdp是oracle数据库之间移动数据的工具。expdp和

连续4年 华为依然是“高交会”上最靓的仔

2019年11月13-17日,由科技部、国家发改委等10个部委和深圳市人民政府共同举办的2019中国国际高新技术成果交易会(高交会)在深圳举办。这是华为连续第4年参加高交会智慧城市展览,不仅如此,华为

史上最快AI计算机发布!谷歌TPU V3的1/5功耗、1/30体积,首台实体机已交付

大数据文摘作品还记得8月份占据各家科技头条的有史以来最大芯片吗?这个名为CerebrasWaferScaleEngine(WSE)的“巨无霸”面积达到42225平方毫米,拥有1.2万亿个晶体管,400

我的天!这是史上最烂的项目:苦撑12年,600多万行代码…

编译:欧剃来源:projectfailures.wordpress.com转载自:Java技术栈你见过最烂的项目,撑了多长时间才完蛋?六个月?一年?今天介绍的这个奇葩项目,不但一开始就烂得透透的,还硬