1、扫码下载最新版Java 开发手册阿里云开发者“藏经阁”海量免费电子书下载扫码关注 Hollis一个对 Coding 有着独特追求的人新版 Java 开发手册提到的三目运算符的空指针问题到底是个怎么回事?4为什么阿里巴巴建议初始化 HashMap 的容量大小?15Java 开发手册建议创建 HashMap 时设置初始化容量,但是多少合适呢?27为什么阿里巴巴禁止使用 Executors 创建线程池?31为什么阿里巴巴要求谨慎使用 ArrayList 中的 subList 方法?37为什么阿里巴巴不建议在 for 循环中使用“+”进行字符串拼接?44为什么阿里巴巴禁止在foreach循环里进行元素
2、的remove/add操作?54为什么阿里巴巴禁止工程师直接使用日志系统(Log4j、Logback)中的API?66为什么阿里巴巴禁止把 SimpleDateFormat 定义成 static 变量?74为什么阿里巴巴禁止开发人员使用 isSuccess 作为变量名?85为什么阿里巴巴禁止开发人员修改 serialVersionUID字段的值?97为什么阿里巴巴建议开发者谨慎使用继承?109为什么阿里巴巴禁止使用count(列名)或count(常量)来替代count(*)?111目录新版 Java 开发手册提到的三目运算符的空指针问题到底是个怎么回事?作者|Hollis作者简介:Hollis
3、(个人公众号 Id:Hollis),一个对 Coding 有着独特追求的人,现任阿里巴巴技术专家,个人技术博主,技术文章全网阅读量数千万,程序员的三门课联合作者。最近,阿里巴巴 Java 开发手册发布了最新版泰山版,这个名字起的不错,一览众山小。新版新增了 30+规约,其中有一条规约引起了作者的关注,那就是手册中提到在三目运算符使用过程中,需要注意自动拆箱导致的 NullPointerException(后文简称:NPE)问题:因为这个问题我很久之前(2015 年)遇到过,曾经在博客中也记录过,刚好最新的开发手册再次提到了这个知识点,于是把之前的文章内容翻出来并重新整理了一下,带大家一起回顾下
4、这个知识点。新版 Java 开发手册提到的三目运算符的空指针问题到底是个怎么回事?5可能有些人看过我之前那篇文章,本文并不是单纯的”旧瓶装新酒”,在重新梳理这个知识点的时候,作者重新翻阅了TheJavaLanguageSpecification,并且对比了 JavaSE7和JavaSE8 之后的相关变化,希望可以帮助大家更加全面的理解这个问题。基础回顾在详细展看介绍之前,先简单介绍下本文要涉及到的几个重要概念,分别是”三目运算符”、”自动拆装箱”等,如果大家对于这些历史知识有所掌握的话,可以先跳过本段内容,直接看问题重现部分即可。三目运算符在TheJavaLanguageSpecificati
5、on 中,三 目 运 算 符 的 官 方 名 称 是Conditional Operator?:,我一般称呼他为条件表达式,详细介绍在 JLS15.25 中,这里简单介绍下其基本形式和用法:三目运算符是 Java 语言中的重要组成部分,它也是唯一有 3 个操作数的运算符。形式为:?:以上,通过?、:组合的形式得到一个条件表达式。其中?运算符的含义是:先求表达式 1 的值,如果为真,则执行并返回表达式 2 的结果;如果表达式 1 的值为假,则执行并返回表达式 3 的结果。值得注意的是,一个条件表达式从不会既计算,又计算。条件运算符是右结合的,也就是说,从右向左分组计算。例如,a?b:c?d:e
6、将按a?b:(c?d:e)执行。6 新版 Java 开发手册提到的三目运算符的空指针问题到底是个怎么回事?自动装箱与自动拆箱介绍过了三目运算符(条件表达式)之后,我们再来简单介绍下 Java 中的自动拆装箱相关知识点。每一个 Java 开发者一定都对 Java 中的基本数据类型不陌生,Java 中共有 8种基本数据类型,这些基础数据类型带来一个好处就是他们直接在栈内存中存储,不会在堆上分配内存,使用起来更加高效。但是,Java 语言是一个面向对象的语言,而基本数据类型不是对象,导致在实际使用过程中有诸多不便,如集合类要求其内部元素必须是 Object 类型,基本数据类型就无法使用。所以,相对应
7、的,Java 提供了 8 种包装类型,更加方便在需要对象的地方使用。有了基本数据类型和包装类,带来了一个麻烦就是需要在他们之间进行转换。在Java SE5 中,为了减少开发人员的工作,Java 提供了自动拆箱与自动装箱功能。自动装箱:就是将基本数据类型自动转换成对应的包装类。自动拆箱:就是将包装类自动转换成对应的基本数据类型。Integer i=10;/自动装箱 int b=i;/自动拆箱我们可以简单理解为,当我们自己写的代码符合装(拆)箱规范的时候,编译器就会自动帮我们拆(装)箱。自动装箱都是通过包装类的 valueOf()方法来实现的.自动拆箱都是通过包装类对象的 xxxValue()来实
8、现的(如 booleanValue()、longValue()等)。新版 Java 开发手册提到的三目运算符的空指针问题到底是个怎么回事?新版 Java 开发手册提到的三目运算符的空指针问题到底是个怎么回事?那么,为什么编译器会进行自动拆箱呢?什么情况下需要进行自动拆箱呢?原理分析关于为什么编辑器会在代码编译阶段对于三目运算符中的表达式进行自动拆箱,其实在TheJavaLanguageSpecification(后文简称 JLS)的第 15.25 章节中是有相关介绍的。在不同版本的 JLS 中,关于这部分描述虽然不尽相同,尤其在 Java8 中有了大幅度的更新,但是其核心内容和原理是不变的。我
9、们直接看 JavaSE1.7JLS 中关于这部分的描述(因为 1.7 的表述更加简洁一些):Thetypeofaconditionalexpressionisdeterminedasfollows:Ifthesecondandthirdoperandshavethesametype(whichmaybethenulltype),thenthatisthetypeoftheconditionalexpression.IfoneofthesecondandthirdoperandsisofprimitivetypeT,andthetypeoftheotheristheresultofapplyin
10、gboxingconversion(5.1.7)toT,thenthetypeoftheconditionalexpressionisT.简单的来说就是:当第二位和第三位操作数的类型相同时,则三目运算符表达式的结果和这两位操作数的类型相同。当第二,第三位操作数分别为基本类型和该基本类型对应的包装类型时,那么该表达式的结果的类型要求是基本类型。为了满足以上规定,又避免程序员过度感知这个规则,所以在编译过程中编译器如果发现三目操作符的第二位和第三位操作数的类型分别是基本数据类型(如boolean)以及该基本类型对应的包装类型(如 Boolean)时,并且需要返回表达式为包装类型,那么就需要对该包装
11、类进行自动拆箱。在 JavaSE1.8JLS 中,关于这部分描述又做了一些细分,再次把表达式区分成布尔型条件表达式(BooleanConditionalExpressions)、数值型条件表达式(NumericConditionalExpressions)和引用类型条件表达式(Reference新版 Java 开发手册提到的三目运算符的空指针问题到底是个怎么回事?新版 Java 开发手册提到的三目运算符的空指针问题到底是个怎么回事?/当第二位和第三位表达式中有一个为基本类型时,表达式返回值也为基本类型;boolean x3=flag?objectBoolean:simpleBoolean;/反
12、编译后代码为:boolean x3=flag?objectBoolean.booleanValue():simpleBoolean;/因为 x3 的类型是基本类型,所以需要对其中的包装类进行拆箱。因为我们熟知三目运算符的规则,所以我们就会按照以上方式去定义 x1、x2 和x3 的类型。但是,并不是所有人都熟知这个规则,所以在实际应用中,还会出现以下三种定义方式:/当第二位和第三位表达式都是对象时,表达式返回值也为对象;boolean x4=flag?objectBoolean:objectBoolean;/反编译后代码为:boolean x4=(flag?objectBoolean:objec
13、tBoolean).booleanValue();/因为 x4 的类型是基本类型,所以需要对表达式结果进行自动拆箱。/当第二位和第三位表达式都为基本类型时,表达式返回值也为基本类型;Boolean x5=flag?simpleBoolean:simpleBoolean;/反编译后代码为:Boolean x5=Boolean.valueOf(flag?simpleBoolean:simpleBoolean);/因为 x5 的类型是对象类型,所以需要对表达式结果进行自动装箱。/当第二位和第三位表达式中有一个为基本类型时,表达式返回值也为基本类型;Boolean x6=flag?objectBool
14、ean:simpleBoolean;/反编译后代码为:Boolean x6=Boolean.valueOf(flag?objectBoolean.booleanValue():simpleBoolean);/因为 x6 的类型是对象类型,所以需要对表达式结果进行自动装箱。新版 Java 开发手册提到的三目运算符的空指针问题到底是个怎么回事?新版 Java 开发手册提到的三目运算符的空指针问题到底是个怎么回事?所以,Java 开发手册中提到要高度注意第二位和第三位表达式的类型对齐过程中由于自动拆箱发生的 NPE 问题,其实还需要注意使用三目运算符表达式给变量赋值的时候由于自动拆箱导致的 NPE
15、问题。至此,我们已经介绍完了 Java 开发手册中关于三目运算符使用过程中可能会导致 NPE 的问题。如果一定要给出一个方法论去避免这个问题的话,那么在使用的过程中,无论是三目运算符中的三个表达式,还是三目运算符表达式要赋值的变量,最好都使用包装类型,可以减少发生错误的概率。正文内容已完,如果大家对这个问题还有更深的兴趣的话,接下来部分内容是扩展内容,也欢迎学习,不过这部分涉及到很多 JLS 的规范,如果实在看不懂也没关系 扩展思考为了方便大家理解,我使用了简单的布尔类型的例子说明了 NPE 的问题。但是实际在代码开发中,遇到的场景可能并没有那么简单,比如说以下代码,大家猜一下能否正常执行:M
16、ap map=new HashMap();Boolean b=(map!=null?map.get(“Hollis”):false);如果你的答案是 不能,这里会抛 NPE 那么说明你看懂了本文的内容,但是,我只能说你只是答对了一半。因为以上代码,在小于 JDK1.8 的版本中执行的结果是 NPE,在 JDK1.8及以后的版本中执行结果是 null。之所以会出现这样的不同,这个就说来话长了,我挑其中的重点内容简单介绍下目录目录 Boolean b=maps=null?Boolean.valueOf(false):(Boolean)maps.get(Hollis);但是在 Java7 中可没有这些规定(Java8 之前的类型推断功能还很弱),编译器只知道表达式的第二位和第三位分别是基本类型和包装类型,而无法推断最终表达式类型。那么他就会先根据 JLS15.25 的规定,把返回值结果转换成基本类型。然后在进行变量赋值的时候,再转换成包装类型:Boolean b=Boolean.valueOf(maps=null?false:(Boolean)maps.get(Hollis).boolean