Java语言暗箱操作之自动装箱与拆箱

  我以前在写Android项目的时候,估计写得最多最熟练的几句话就是:

  

List<Integer> list = new ArrayList<Integer>();
list.add(1);   //把一个整数加入到集合中
int i = list.get(0);    //从集合中取出元素

  ArrayList用起来是多么的顺手!当时我只知道尖括号<>里面只能加入大写字母开头的Object类型,不能加入int、char、double这些原始类型,至于原因没研究过,这么规定就这么用呗。

  但是随着对“码农”式无脑学习法的逐渐厌倦,我开始重新审视Java代码内部的东西。

  首当其冲的就是每个项目一定用到的ArrayList。在我的另一篇博客中已经对ArrayList的源码实现做了大体的分析。然而还有几个源码中看不出来,但是确实存在疑点的问题亟待解决。

  

List<Integer> list = new ArrayList<Integer>();

这句代码中每个元素是Integer类型,那么往list里面add新元素的时候必须为Integer,比如加个String进去,代码下面就会出现红色波浪线。但是这句list.add(1) 众所周知,代码里面随便写个不带小数点的数字,那它就是个int;把一个int加到一个只能有Integer的List中不报错,不觉得有猫腻吗?

同样地,int i = list.get(0),取出list中索引为0的元素,也应该是个Integer,为什么接收的变量就是个int呢?这是一个多么明显的类型不匹配错误啊!

以前,我确实听说过“包装类”这个概念,但是忽视了它,因为我一直觉得Integer,Float这些东西,说难听点就是摆出来装装逼的,只是因为List不接受int,float类型,迫不得已发明了Integer,Float,实际并没有卵用。

最近看了《Effective Java》里面的一节,名字叫“Prefer primitive types to boxed primitives”。里面罗列了很多原始类型和包装类型混用的例子,搞得我晕头转向的。下面是其中一段代码:
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
     sum += i;
}
System.out.println(sum);

据书中讲,这是一段运行效率低到不可救药的代码,你能看出其中的问题吗?反正我当时看到这段代码就明显感觉到,Java对于原始类型与相应的Object类型的转化,在编译过程中肯定做了什么见不得人的事情……

下面正式引出本文的话题:AutoBoxing and Unboxing(自动装箱&自动拆箱)

看一个最简单的例子:
Character ch = ‘a‘;  //Character是char的包装类

这里没有出现任何错误,其实编译器在代码优化的时候,暗中转化成了下面的代码:
Character ch = Character.valueOf(‘a‘); 

这就是说,"="右侧自动调用Character类对应的静态方法构造出了一个Character的实例。为了进一步说明,这里稍微看一下valueOf方法
    public static Character valueOf(char c) {
        return c < 128 ? SMALL_VALUES[c] : new Character(c);
    }   //如果字符在缓冲区中,直接取出Character实例,否则要重新构造

    private static final Character[] SMALL_VALUES = new Character[128];  //类中自带一个静态的缓冲区,保存128个常用ASCII码字符对应的Character实例,免去每次重新构造实例的麻烦

    static {
        for (int i = 0; i < 128; i++) {
            SMALL_VALUES[i] = new Character((char) i);  //调用构造函数
        }
    }

对于Integer等其他包装类,自身都带有一个静态的valueOf方法。每次编译器检查到需要把一个int传给Integer时,就自动对代码进行转化。比如上面的list.add(1),在编译过程中编译器发现要传进去的参数是int,但是要接收的是Integer,于是代码变为:
list.add(Integer.valueOf(1));

以上就是自动装箱(auto-boxing)的过程。

自动装箱一般在两种情况下会发生(以int和Integer为例):1、把int作为一个方法的参数传进来,但是方法体里面希望得到的参数是Integer;2、在赋值过程中,"="左边是Integer变量,右边是int变量。

这样一来,自动拆箱的过程就顺理成章了。看以下代码:
public static int sumEven(List<Integer> li) {
    int sum = 0;
    for (Integer i: li)
        if (i % 2 == 0)
            sum += i;
        return sum;
}

在循环体内做了两次拆箱操作,编译器会转换成以下代码:
public static int sumEven(List<Integer> li) {
    int sum = 0;
    for (Integer i: li)
        if (i.intValue() % 2 == 0)
            sum += i.intValue();
        return sum;
}

Integer的intValue方法就简单多了,直接返回被包装的int值
    @Override
    public int intValue() {
        return value;    //value是Integer的成员变量
    }

自动拆箱的用处跟自动装箱正好相反,也是用在参数传递和赋值过程中,这里就不赘述了。

我们再来分析一下那段超级低效的代码吧,经过自动拆装箱转换之后应该是这样子的:
Long sum = Long.valueOf(0L);
for (long i = 0; i < Integer.MAX_VALUE; i++) {
     sum = Long.valueOf(sum.longValue() + i);   //低效所在
}
System.out.println(sum.toString());

在循环体里面,简简单单只有一句话,竟然包含一次拆箱和一次装箱操作,在经过20多亿次的循环之后,效率损耗得难以置信!既然拆箱和装箱可以看做“逆运算”,那么为什么还要进行多余的操作呢?直接用原始值运算,然后一次装箱不是更省事吗
Long sum = 0L;
long s = sum;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
     s += i;
}
sum = Long.valueOf(s);
System.out.println(sum);

参考资料:https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html
时间: 2024-08-30 11:15:10

Java语言暗箱操作之自动装箱与拆箱的相关文章

Java基础之包装类的自动装箱和拆箱详解

定义 在java中,数据类型可以分为两大类,即基本数据类型和引用数据类型,基本数据类型的数据不是对象,所以对于要将数据类型作为对象来使用的情况,java提供了相对应的包装类.(关于包装类的详细介绍请参看博客Java基础之常用类详解) 本篇博客主要讲述包装类的自动装箱和拆行机制. 所谓装箱,就是把基本数据类型用它们相对应的引用类型包起来,使它们可以具有对象的特质,如我们可以把int类型包装成Integer类型的对象,或者把double包装秤Double,等等. 所谓拆箱,就是和装箱的方向相反,将I

自动装箱与拆箱引发的享元设计模式

[java] view plain copy /** * 自动装箱与拆箱 */ public class Autoboxing { public static void main(String[] args) { Integer num1 = 20;          //自动装箱 int num2 = new Integer(20);     //自动拆箱 System.out.println(num2 == num1);   //true //-128~127之间的同一个Intege对象相比

[转]java 自动装箱与拆箱

转自:http://www.cnblogs.com/shenliang123/archive/2012/04/16/2451996.html 这个是jdk1.5以后才引入的新的内容,作为秉承发表是最好的记忆,毅然决定还是用一篇博客来代替我的记忆: java语言规范中说道:在许多情况下包装与解包装是由编译器自行完成的(在这种情况下包装成为装箱,解包装称为拆箱): 其实按照我自己的理解自动装箱就可以简单的理解为将基本数据类型封装为对象类型,来符合java的面向对象:例如用int来举例: //声明一个

【转】java 自动装箱与拆箱

java 自动装箱与拆箱 这个是jdk1.5以后才引入的新的内容,作为秉承发表是最好的记忆,毅然决定还是用一篇博客来代替我的记忆: java语言规范中说道:在许多情况下包装与解包装是由编译器自行完成的(在这种情况下包装成为装箱,解包装称为拆箱): 其实按照我自己的理解自动装箱就可以简单的理解为将基本数据类型封装为对象类型,来符合java的面向对象:例如用int来举例: //声明一个Integer对象 Integer num = 10; //以上的声明就是用到了自动的装箱:解析为 Integer

Java中自动装箱与拆箱详解

在讲装箱与拆箱之前我们要先了解一下这个问题的来源: Java中的类型分为基本类型(Primitive type)和类类型(Class type)两种: 基本类型包括byte型.char型.short型.int型.long型.float型.double型.boolean型八种.基本类型指的是直接包含值得类型,可提供语言级别的支持. 类类型包括Byte.Character.Short.Integer.Long.Float.Double.Boolean.为什么要用类类型呢?原因其实很简单,因为Java

《Java中的自动装箱和拆箱功能.》

1 //Java中的自动装箱和拆箱功能. 2 class AutoboxingUnboxing 3 { 4 public static void main(String[] args) 5 { 6 //直接把一个基本类型变量赋给Interger对象. 7 Integer inObj = 5; 8 Object inObj1 = 6; 9 //直接把一个boolean类型的变量赋给一个Object类型的变量. 10 Object boolObj = true; 11 //直接把Integer对象赋

java自动装箱与拆箱

基本数据(Primitive)类型的自动装箱(autoboxing).拆箱(unboxing)是J2SE 5.0提供的新功能,跟泛型.变长参数等一样,这也是一颗"语法糖",之前介绍jvm早期优化的时候说过,语法糖对运行期的代码没有任何影响,其目的仅仅是方便程序员使用,所有语法糖编译后都将会还原成基础语法.这次介绍的自动装箱和拆箱也不例外.看下头的例子: public class Demo { public static void main(String[] args) { Intege

Java 自动装箱和拆箱

JDK1.5之后的功能 自动装箱:指的是开发人员可以把一个基本数据类型直接赋给对应的包装类 自动拆箱:指开发人员可以把一个包装类对象直接赋给对应的基本数据类型 public static void main(String[] args) { Integer i = 1; //装箱 int j = i ; //拆箱 } public static void main(String[] args) { List list = new ArrayList(); list.add(1); int j=

深入剖析Java中的自动装箱和拆箱过程

深入剖析Java中的装箱和拆箱 自动装箱和拆箱问题是Java中一个老生常谈的问题了,今天我们就来一些看一下装箱和拆箱中的若干问题.本文先讲述装箱和拆箱最基本的东西,再来看一下面试笔试中经常遇到的与装箱.拆箱相关的问题. 以下是本文的目录大纲: 一.什么是装箱?什么是拆箱? 二.装箱和拆箱是如何实现的 三.面试中相关的问题 若有不正之处,请谅解和批评指正,不胜感激. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/dolphin0520/p/3780005.