NullPointerException 的处理新方式,Java14 真的太香了!

InfoQ 2021-02-02 01:25:07
NullPointerException 方式 处理 新方 java14


{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Java语言中,处理空指针往往是一件很头疼的事情,一不小心,说不定就搞出个线上Bug,让你的绩效考核拿到3.25。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最近新出的Java14,相信大家都有所耳闻,那么今天就来看看,面对NullPointerException,Java14有哪些更好的处理方式呢?","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1.传统的 NullPointerException","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们编码过程中呢,经常会使用链式调用的方式来写代码,这样写起来很方便,也很清晰,但是,一旦出现NullPointerException,那就头大了,因为你很难知道异常是在什么时候开始发生的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"举个简单的例子,就比如下面的代码,要找到公司某个员工的户籍所在地,我们这样来调用","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"String city = employee.getDetailInfos().getRegistryAddress().getCity();","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在链式调用的过程中,如果","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"employee","attrs":{}}],"attrs":{}},{"type":"text","text":", ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getDetailInfos()","attrs":{}}],"attrs":{}},{"type":"text","text":",或者 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getRegistryAddress()","attrs":{}}],"attrs":{}},{"type":"text","text":" 为空,JVM就会抛出 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"NullPointerException","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那么导致异常的根本原因是什么?如果不使用调试器,很难确定哪个变量为空。而且,JVM也只会打印导致异常的方法、文件名和行号,仅此而已。那么下面,我将带大家了解Java 14如何通过 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"JEP 358","attrs":{}}],"attrs":{}},{"type":"text","text":" 解决这个问题。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"2.增强型 NullPointerException","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SAP在2006年为其商业JVM实现了增强型的 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"NullPointerException","attrs":{}},{"type":"text","text":"。2019年2月,它被提议作为OpenJDK社区的一个增强,之后很快,它成为了一个JEP。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"所以,该功能在2019年10月完成并在JDK 14版本推出","attrs":{}},{"type":"text","text":"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本质上,JEP 358 旨在通过描述某个变量是 “","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"null”","attrs":{}},{"type":"text","text":" 来提高 JVM 生成的 “NullPointerException” 的可读性。JEP 358通过在方法、文件名和行号旁边描述为 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"null","attrs":{}},{"type":"text","text":" 的变量,带来了一个详细的 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"NullPointerException","attrs":{}},{"type":"text","text":" 消息。它通过分析程序的字节码指令来工作。因此,它能够精确地确定哪个变量或表达式是","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"null","attrs":{}},{"type":"text","text":"。最重要的是,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"JDK 14中默认关闭详细的异常消息","attrs":{}},{"type":"text","text":"。要启用它,我们需要使用命令行选项:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"-XX:+ShowCodeDetailsInExceptionMessages","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2.1 详细的异常信息","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"考虑在激活 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"ShowCodeDetailsInExceptionMessages","attrs":{}}],"attrs":{}},{"type":"text","text":" 标志的情况下再次运行代码:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"Exception in thread \"main\" java.lang.NullPointerException:\n Cannot invoke \"RegistryAddress.getCity()\" because the return value of\n\"com.developlee.java14.helpfulnullpointerexceptions.HelpfulNullPointerException$DetailInfos.getRegistryAddress()\" is null\n at com.developlee.java14.helpfulnullpointerexceptions.HelpfulNullPointerExc","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这一次,从附加信息中,我们知道员工的个人详细信息丢失的注册地址导致了我们的异常。从这个增强中获得的信息可以节省我们调试所用的时间。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JVM由两部分组成详细的异常消息。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"第一部分表示失败的操作,这是引用为 *null* 的结果,而第二部分标识了 *null* 引用","attrs":{}},{"type":"text","text":"的原因:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Cannot invoke \"String.toLowerCase()\" because the ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"return","attrs":{}},{"type":"text","text":" value of \"getEmailAddress()\" is ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"null","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了生成异常消息,JEP 358 重构了将空引用推送到操作数堆栈上的部分源代码。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"3. 技术方面","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现在我们已经很好地理解了如何使用增强的","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"NullPointerException","attrs":{}},{"type":"text","text":"s标识 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"null","attrs":{}},{"type":"text","text":" 引用,让我们来看看它的一些技术方面。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,只有当JVM本身抛出一个 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"NullPointerException","attrs":{}},{"type":"text","text":" 时,才会进行详细的消息计算,如果我们在Java代码中显式抛出异常,则不会执行计算。原因是因为:在这些情况下,很可能已经在异常构造函数中传递了一条有意义的消息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"JEP 358","attrs":{}},{"type":"text","text":"懒汉式地计算消息,这意味着只有当我们打印异常消息时才调用增强的NullPointerException,而不是当异常发生时就调用。因此,对于通常的JVM流程不应该有任何性能影响,在那里我们可以捕获并重新抛出异常,因为咱并不会只想打印异常消息。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"详细的异常消息可能包含源代码","attrs":{}},{"type":"text","text":"中的局部变量名。因此,我们可以认为这是一个潜在的安全风险。但是,只有在运行使用激活的 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"-g","attrs":{}},{"type":"text","text":" 标记编译的代码时,才会发生这种情况,该标记会生成调试信息并将其添加到类文件中。请考虑一个简单的示例,我们已编译该示例以包含以下附加调试信息:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Employee employee = ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"null","attrs":{}},{"type":"text","text":";","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"employee.getName();","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当执行以上代码时,异常信息中会打印本地变量名称:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"\"com.developlee.java14.helpfulnullpointerexceptions.HelpfulNullPointerException$Employee.getName()\"","attrs":{}}],"attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"because \"employee\" is ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"null","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相反,在没有额外调试信息的情况下,JVM 只提供它在详细消息中所知道的变量:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Cannot invoke","attrs":{}}],"attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"  \"com.developlee.java14.helpfulnullpointerexceptions.HelpfulNullPointerException$Employee.getName()\"","attrs":{}}],"attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"because \"\" is ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"null","attrs":{}}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JVM 打印编译器分配的变量索引,而不是本地变量名(employee)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关于NullPointerException的处理到这里就结束了,通过Java14增强的NullPointerException,我们可以很快速的定位代码问题的原因所在,更快的调试代码,节约时间,提高效率。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"已经安装了Java14的朋友可以试试看哦~","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://xie.infoq.cn/article/67fbc99144ba7c5df3c1627a1?utm_source=rss&utm_medium=article

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云