Effective Java 第三版——61. 基本类型优于装箱的基本类型
Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

61. 基本类型优于装箱的基本类型
Java是一个由两部分类型组成的系统,一部分由基本类型组成,如int,double和boolean,还有一部分是引用类型,如String和List。 每个基本类型都有一个相应的引用类型,称为装箱基本类型。 对应于int,double和boolean的包装基本类型是Integer,Double和Boolean。
正如条目6中提到的,自动装箱和自动拆箱模糊了基本类型和装箱基本类型之间的区别,但不会消除它们。这两者之间有真正的区别,重要的是要始终意识到你正在使用的是哪一种,并在它们之间仔细选择。
基本类型和包装基本类型之间有三个主要区别。首先,基本类型只有它们的值,而包装基本类型具有与其值不同的标识。换句话说,两个包装基本类型实例可以具有相同的值但不同的引用标识。第二,基本类型只有功能的值(functional value),而每个包装基本类型类型除了对应的基本类型的功能值外,还有一个非功能值,即null。最后,基本类型比包装的基本类型更节省时间和空间。如果你不小心的话,这三种差异都会给你带来真正的麻烦。
考虑下面的比较器,它的设计目的是表示Integer值的升序数字顺序。(回想一下,比较器的compare方法返回一个负数、零或正数,这取决于它的第一个参数是小于、等于还是大于第二个参数)。你不需要在实践中编写这个比较器,因为它实现了Integer的自然排序,但它提供了一个有趣的例子:
// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder =
(i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
这个比较器看起来应该工作,也能通过很多测试。 例如,它可以与Collections.sort方法一起使用,以正确排序百万个元素列表,无论列表是否包含重复元素。 但这个比较器存在严重缺陷。 为了说服自己,只需打印naturalOrder.compare(new Integer(42),new Integer(42))的值。 两个Integer实例都表示相同的值(42),因此该表达式的值应为0,但它为1,表示第一个Integer值大于第二个值!
那么问题出在哪里呢?naturalOrder中的第一个测试工作得很好。计算表达式i < j会使i和j引用的整数实例自动拆箱;也就是说,它提取它们的基本类型值。计算的目的是检查得到的第一个int值是否小于第二个int值。但假设是否定的。然后,下一个测试计算表达式i==j,该表达式对两个对象执行引用标识比较。如果i和j引用表示相同整型值的不同Integer实例,这个比较将返回false,比较器将错误地返回1,表明第一个整型值大于第二个整型值。将==操作符应用于装箱的基本类型几乎总是错误的。
在实践中,如果你需要一个比较器来描述类型的自然顺序,应该简单地调用comparator . naturalorder()方法,如果自己编写一个比较器,应该使用比较器构造方法,或者对基本类型使用静态compare方法(条目 14)。也就是说,可以通过添加两个局部变量来存储与装箱Integer参数对应的原始int值,并对这些变量执行所有的比较,从而修复了损坏的比较器中的问题。这样避免了错误的引用一致性比较:
Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
int i = iBoxed, j = jBoxed; // Auto-unboxing
return i < j ? -1 : (i == j ? 0 : 1);
};
接下来,考虑一下这个有趣的小程序:
public class Unbelievable {
static Integer i;
public static void main(String[] args) {
if (i == 42)
System.out.println("Unbelievable");
}
}
它不会打印出Unbelievable字符串——但它所做的事情几乎同样奇怪。它在计算表达式i==42时抛出NullPointerException。问题是,i是Integer类型,而不是int类型,而且像所有非常量对象引用属性一样,它的初始值为null。当程序计算表达式i==42时,它是在比较Integer和int之间的关系。 几乎在每种情况下,当在基本类型和包装基本类型进行混合操作时,包装基本类型会自动拆箱。如果对一个null对象进行自动拆箱,那么会抛出NullPointerException。正如这个程序所演示的,它几乎可以在任何地方发生。修复这个问题非常简单,只需将i声明为int而不是Integer就可以了。
最后,考虑第24页条目6中的程序:
// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
这个程序比它原本的速度慢得多,因为它意外地声明了一个局部变量(sum),它是装箱的基本类型Long,而不是基本类型long。程序在没有错误或警告的情况下编译,变量被反复装箱和拆箱,导致观察到的性能下降。
在本条目中讨论的所有三个程序中,问题都是一样的:程序员忽略了基本类型和包装基本类型之间的区别,并承担了后果。在前两个项目中,结果是彻底的失败;第三,严重的性能问题。
那么,什么时候应该使用装箱基本类型呢?它们有几个合法的用途。第一个是作为集合中的元素、键和值。不能将基本类型放在集合中,因此必须使用装箱的基本类型。这是一般情况下的特例。在参数化类型和方法(第5章)中,必须使用装箱基本类型作为类型参数,因为该语言不允许使用基本类型。例如,不能将变量声明为ThreadLocal<int>类型,因此必须使用ThreadLocal<Integer>。最后,在进行反射方法调用时,必须使用装箱基本类型(条目 65)。
总之,只要有选择,就应该优先使用基本类型,而不是装箱基本类型。基本类型更简单、更快。如果必须使用装箱基本类型,则需要小心!自动装箱减少了使用装箱基本类型的冗长,但没有降低使用的危险。当程序使用==操作符比较两个装箱的基本类型时,它会执行引用标识比较,这几乎肯定不是你想要的。当程序执行包含装箱和拆箱基本类型的混合类型计算时,它会执行拆箱,当程序执行拆箱时,会抛出NullPointerException。最后,当程序装箱了基本类型,可能会导致代价高昂且创建了不必要的对象。
Effective Java 第三版——61. 基本类型优于装箱的基本类型的更多相关文章
- Effective Java 第三版——42.lambda表达式优于匿名类
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——43.方法引用优于lambda表达式
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——62. 当有其他更合适的类型时就不用字符串
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Effective Java 第三版——58. for-each循环优于传统for循环
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- 《Effective Java 第三版》目录汇总
经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...
- 《Effective Java 第三版》新条目介绍
版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...
- Effective Java 第三版——22. 接口仅用来定义类型
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——34. 使用枚举类型替代整型常量
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——26. 不要使用原始类型
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
随机推荐
- #12【BZOJ3003】LED BFS+状压DP
题解: 看到区间修改先想一下差分 这题用差分是为了分析问题 现在的问题就变成了 原序列全为0,要使得特定的k个点变为1,每个操作改变x,y+1 然后我们会发现 对于二元组a,b我们要修改它,实际上是在 ...
- BZOJ1786 [Ahoi2008]Pair 配对 动态规划 逆序对
欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ1786 题意概括 给出长度为n的数列,只会出现1~k这些正整数.现在有些数写成了-1,这些-1可以变 ...
- 【Java】 剑指offer(47) 礼物的最大价值
本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集 题目 在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值( ...
- Python开发之pip使用详解
1 pip的优点 pip如今已经成为了Python的一大特色,可以很方便得协助Python开发者进行包管理.综合来说,匹配拥有如下优点: pip提供了丰富的功能,其竞争对手easy_install只支 ...
- MyBatis学习笔记3--使用XML配置SQL映射器
<resultMap type="Student" id="StudentResult"> <id property="id&quo ...
- iOS11开发教程(二十三)iOS11应用视图实现按钮的响应(3)
iOS11开发教程(二十三)iOS11应用视图实现按钮的响应(3) 2.使用代码添加按钮实现的响应 使用代码添加的按钮,实现响应需要使用到addTarget(_:action:for:)方法,其语法形 ...
- swagger知识点补充
1. swagger知识点补充 1.1. 概述 在swagger的使用过程中,除了网上常见的例子,还会有很多细节上的东西需要注意和改写,这里我列几点我使用过程中遇到的问题和改进方式 1.2. 知识点 ...
- BZOJ1330 : Editing a Book
注意到答案不超过$5$,因此可以考虑BFS求出距离起始态或者终止态不超过$2$的所有状态. 设它们到起始态.终止态的距离分别为$f[S],g[S]$,则$ans=\min(5,f[S]+g[S])$. ...
- VMware workstation12 密匙
VMware workstation12 密匙:5A02H-AU243-TZJ49-GTC7K-3C61N
- ubuntu.windows桌面远程ubuntu的方法(附带安装图形桌面--可选)
(1)首先通过Windows系统下连接Linux系统的命令行工具连接Ubuntu服务器,(工具:xshell,securecrt,putty等). (2)通过Windows下工具连接到Linux操作系 ...