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. 基本类型优于装箱的基本类型的更多相关文章

  1. Effective Java 第三版——42.lambda表达式优于匿名类

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  2. Effective Java 第三版——43.方法引用优于lambda表达式

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  3. Effective Java 第三版——62. 当有其他更合适的类型时就不用字符串

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  4. Effective Java 第三版——58. for-each循环优于传统for循环

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  5. 《Effective Java 第三版》目录汇总

    经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...

  6. 《Effective Java 第三版》新条目介绍

    版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...

  7. Effective Java 第三版——22. 接口仅用来定义类型

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  8. Effective Java 第三版——34. 使用枚举类型替代整型常量

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  9. Effective Java 第三版——26. 不要使用原始类型

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

随机推荐

  1. java:合并两个排序的整数数组A和B变成一个新的数组。新数组也要有序。

    合并两个排序的整数数组A和B变成一个新的数组.新数组也要有序. 样例 1: 输入: A=[1], B=[1] 输出:[1,1] 样例解释: 返回合并后的数组. 样例 2: 输入: A=[1,2,3,4 ...

  2. L3-016 二叉搜索树的结构 (30 分) 二叉树

    二叉搜索树或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值:若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值:它的左.右子树也分别 ...

  3. 《Gradle权威指南》--Android Gradle多项目构建

    No1: Android多项目设置 目录结构: MyProject/ setting.gradle app/ build.gradle libraries/ lib1/ build.gradle li ...

  4. 三篇文章带你极速入门php(一)之语法

    本文适合阅读用户 有其他语言基础的童鞋 看完w3cschool语法教程来回顾一下的童鞋(传送门,想全面看一下php语法推荐这里) 毫无基础然而天资聪慧颇有慧根(不要左顾右看说的就是你,老夫这里有一本& ...

  5. 洛谷.1251.餐巾计划问题(费用流SPFA)

    题目链接 /* 每一天的餐巾需求相当于必须遍历某些点若干次 设q[i]为Dayi需求量 (x,y)表示边x容y费 将每个点i拆成i,i',由i'->T连(q[i],0)的边,表示求最大流的话一定 ...

  6. 在web.xml中添加配置解决hibernate 懒加载异常

    在web.xml添加如下,注意:在配置在struts2的拦截器之前,只能解决请求时出现的懒加载异常:如果没有请求,还需要lazy属性的添加(比如过滤器) <!-- 配置Spring的用于解决懒加 ...

  7. BZOJ3682 Phorni 后缀平衡树

    后缀平衡树的裸题 后缀平衡树简单的思想如下 具体的可以去看$clj$的论文 假设我们已经有了串$S$的后缀平衡树 插入一个字母$c$ 我们用$Si$代表原串$S$从第$i$个字符开始的后缀 则后缀$c ...

  8. Shell中的>/dev/null 2>&1 与 2>&1 >/dev/null 与&>/dev/null 的区别

    默认情况下,总是有三个文件处于打开状态,标准输入(键盘输入).标准输出(输出到屏幕).标准错误(也是输出到屏幕),它们分别对应的文件描述符是0,1,2 .那么我们来看看下面的几种重定向方法的区别: & ...

  9. CocosCreator小栗子

    主要是了解了场景切换的API,见下: 开始场景和结束场景内,按钮挂相同的Btn脚本,主要是切换到场景2中: Btn脚本如下, onLoad:function() { this.node.on('mou ...

  10. python 爬虫不停换代理

    内网看到的一个小技巧,卧槽,感觉真TM厉害 函数均放到main函数内即可 def get_proxy(): url="http://www.xicidaili.com" req=u ...