浅谈JAVA中拆箱与装箱

一.  什么是装箱?什么是拆箱?

在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:

Integer i = new Integer(10);

在栈中储存引用变量;该引用变量指向在堆中储存的对象i;

而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这样就可以了:

Integer i=10

这个过程中会自动根据数值创建对应的 Integer对象,这就是装箱。

跟装箱对应,就是自动将包装器类型转换为基本数据类型:

Integer i = 10;  //装箱

int n = i;   //拆箱

*装箱:自动将基本数据类型转换成包装器类型

拆箱:包装器类型自动转换成基本数据类型

基本数据类型对应包装类:

Btye(btye 1个字节)、Short(short 2个字节)、Integer(int 4个字节)、Long(long 8个字节)、Character(char 2个字节)、Float(float 4个字节)、Double(double 8个字节);

二.  装箱和拆箱是如何实现的

以Interger类为例,下面看一段代码:

public class Main {

public static void main(String[] args) {

Integer i = 10;

int n = i;

}

}

通过反编译去看(原文作者通过反编译javap去看的,我们可以通过反编译工具JD-GUI或者XJAD去查看.class文件都可以根据个人习惯)不上图了(偷懒,哈哈)

从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是Integer的valueOf(int)方法。而在拆箱的时候自动调用的是Integer的intValue方法。

  其他的也类似,比如Double、Character,不相信的朋友可以自己手动尝试一下。

  因此可以用一句话总结装箱和拆箱的实现过程:

装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)

三、实例说明

1、

public class Main {

public static void main(String[] args) {

Integer i1 = 100;

Integer i2 = 100;

Integer i3 = 200;

Integer i4 = 200;

System.out.println(i1==i2);

System.out.println(i3==i4);

}

}

结果:
true

false

输出结果表明i1和i2指向的是同一个对象,而i3和i4指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是Integer的valueOf方法的具体实现:

public static Integer valueOf(int i) {
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
    }

我们来看看IntegerCache类的实现:

private static class IntegerCache {

static final int high;

static final Integer cache[];

static {

final int low = -128;

// high value may be configured by property

int h = 127;

if (integerCacheHighPropValue != null) {

// Use Long.decode here to avoid invoking methods that

// require Integer's autoboxing cache to be initialized

int i = Long.decode(integerCacheHighPropValue).intValue();

i = Math.max(i, 127);

// Maximum array size is Integer.MAX_VALUE

h = Math.min(i, Integer.MAX_VALUE - -low);

}

high = h;

cache = new Integer[(high - low) + 1];

int j = low;

for(int k = 0; k < cache.length; k++)

cache[k] = new Integer(j++);

}

private IntegerCache() {}

}

从这2段代码可以看出,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。

上面的代码中i1和i2的数值为100,因此会直接从cache中取已经存在的对象,所以i1和i2指向的是同一个对象,而i3和i4则是分别指向不同的对象。

缓冲机制

JDK API文档中对这个新的valueOf方法有明确的解释: 
如果不需要新的 Integer 实例,则通常应优先使用该方法,而不是构造方法 Integer(int),因为该方法有可能通过缓存经常请求的值而显著提高空间和时间性能 .

注意,Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。

Double、Float的valueOf方法的实现是类似的。

IntegerCache初始化后内存中就有Integer缓冲区cache[]了,-128~127区间的int值有其对应的的包装对象。这就是 valueOf 方法真正的优化方法:

实例2:

public class ZhuangXaing {        public static void main(String[] args) {
     Integer i= new Integer(12);
        Integer j=12;
        Integer k=Integer.valueOf(12);
        Integer l= new Integer(232);
        Integer m=232;
        Integer n=232;          Double  q = 232.0;
 
        System.out.println("use ==.......");    
        System.out.println(i==12);
        System.out.println(i==j);
        System.out.println(j==k);
 
        System.out.println(l==232);
        System.out.println(l==m);
        System.out.println(m==n);
 
        System.out.println("use equals.....");
        System.out.println(m.equals(n));
        System.out.println(m.equals(q));
 
    }
 
}

输出结果:

use ==.......
true
false
true
true
false
false
use equals.....
true
false
 

Integer i= new Integer(12); 是指明了在堆内存中创建对象; 
Integer j=12; 是自动装箱,调用valueOf 方法,返回return IntegerCache.cache[12 + 128], 得到的是Integer 缓冲池中的对象。Integer k=Integer.valueOf(12); 与Integer j=12; 本质上相同,指向缓冲池中同一对象。包装对象与数值比较,自动拆箱。 
而对于大于127 的数值,执行的都是return new Integer(i) 都在堆内存中,但是地址不同。

equals 方法比较的是数值大小:

public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

可以看出比较的 obj 如果是 Integer 的实例,则比较拆箱后数值的是否相等。否则返回false。

实例3:

public class Main {
    public static void main(String[] args) {
 
        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;
 
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
//false
//false

因为Double类的valueOf方法会采用与Integer类的valueOf方法不同的实现。很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是。

那么这些包装器的缓存范围是多少了?

Integer (-128~127缓存)

Boolean (全部缓存
Byte
(全部缓存)

Character ( <=127 缓存
Short (-128~127
缓存
Long (-128~127
缓存)

Float (没有缓存
Doulbe (
没有缓存)

实例4:

public class Main {
    public static void main(String[] args) {
 
        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;
 
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}
结果:true  true
Boolean的valueOf方法的具体实现:
public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

而其中的 TRUE 和FALSE又是什么呢?在Boolean中定义了2个静态成员属性:

public static final Boolean TRUE = new Boolean(true);
 
    /** 
     * The <code>Boolean</code> object corresponding to the primitive 
     * value <code>false</code>. 
     */
public static final Boolean FALSE = new Boolean(false);
 
四、自我理解的Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。
1)第一种方式不会触发自动装箱的过程;而第二种方式会触发;
第一种是在栈定义了引用变量指向堆里对象,在new 对象时不会去内存里查找是否有重复的数值,都会重新开辟一个新的堆内存;并在栈中有一个引用变量指向该对象。
 
2) 第二种会触发自动装箱的过程,第二种相当于Integer i=Integer.valueOf(XXX);如果该字面值没超出该包装器的缓存范围则会在缓冲区进行缓存,如果定义使用该方法一个相同字面值时会先进缓冲区查找是否有相同的值有就把引用变量指向这个已存在的对象;如果该方式定义的对象取值大于缓存范围就会在在堆内存里开辟空间;虽然都是在堆内存里但地址不同。
在执行效率和资源占用上的区别。第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)
 

实例5:

public class Main {

public static void main(String[] args) {

Integer a = 1;

Integer b = 2;

Integer c = 3;

Integer d = 3;

Integer e = 321;

Integer f = 321;

Long g = 3L;

Long h = 2L;

System.out.println(c==d);

System.out.println(e==f);

System.out.println(c==(a+b));

System.out.println(c.equals(a+b));

System.out.println(g==(a+b));

System.out.println(g.equals(a+b));

System.out.println(g.equals(a+h));

}

}

结果:

true

false

true

true

true

false

true

 第一个和第二个输出结果没有什么疑问。第三句由于  a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。

如果对上面的具体执行过程有疑问,可以尝试获取反编译的字节码内容进行查看。

如果有哪位朋友有补充的内容,如有写错的地方请多指教,欢迎下方留言,不胜感激。

****************************************************************************************************************

本文借鉴大佬学习后自己总结:原文来自出处:http://www.cnblogs.com/dolphin0520/;作者:Matrix海 子;以及https://www.cnblogs.com/linjiaxin/p/6393380.html 加入小部分自己的理解

JAVA中拆箱和装箱的更多相关文章

  1. java 自动拆箱 自动装箱

    自动装箱的定义就是  基本数据类型赋值给包装类型,  拆箱则相反. Integer integer = 122; // 自动装箱 int num = integer; //自动拆箱 想看一下源码是怎么 ...

  2. 聊聊Java中的拆箱和装箱操作

    在刷谷歌面试题的过程中,发现一道有意思的题目,以前没有特别注意,忽略了一些东西,特此记录. 题目要求输出以下代码的结果: public class MyTest { public static voi ...

  3. 享元模式(FlyWeight Pattern)及其在java自动拆箱、自动装箱中的运用

    本文主要从三个方面着手,第一:简要介绍享元模式.第二:享元模式在基本类型封装类中的运用以Integer为例进行阐述.第三:根据第一.第二的介绍,进而推出java是如何实现自动拆箱与装箱的. 第一:简要 ...

  4. 关于Java自动拆箱装箱中的缓存问题

    package cn.zhang.test; /** * 测试自动装箱拆箱 * 自动装箱:基本类型自动转为包装类对象 * 自动拆箱:包装类对象自动转化为基本数据类型 * * * /*缓存问题*/ /* ...

  5. JAVA进阶之旅(一)——增强for循环,基本数据类型的自动拆箱与装箱,享元设计模式,枚举的概述,枚举的应用,枚举的构造方法,枚举的抽象方法

    JAVA进阶之旅(一)--增强for循环,基本数据类型的自动拆箱与装箱,享元设计模式,枚举的概述,枚举的应用,枚举的构造方法,枚举的抽象方法 学完我们的java之旅,其实收获还是很多的,但是依然还有很 ...

  6. Java知多少(24)包装类、拆箱和装箱详解

    虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备“对象”的特性——不携带属性.没有方法可调用. 沿用它们只是为了迎合人类根深蒂固的习惯, ...

  7. [Java学习] Java包装类、拆箱和装箱详解

    虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备“对象”的特性——不携带属性.没有方法可调用. 沿用它们只是为了迎合人类根深蒂固的习惯, ...

  8. Java包装类、拆箱和装箱详解

    转载:https://www.cnblogs.com/ok932343846/p/6749488.html 虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程, ...

  9. 《Java基础知识》Java包装类,拆箱和装箱

    虽然 Java 语言是典型的面向对象编程语言,但其中的八种基本数据类型并不支持面向对象编程,基本类型的数据不具备“对象”的特性——不携带属性.没有方法可调用. 沿用它们只是为了迎合人类根深蒂固的习惯, ...

随机推荐

  1. 40.SEO----前端该懂的seo技巧

    SEO要点:1.语义化html标签,用合适的标签嵌套合适的内容,不可过分依赖div,对浏览器更友善就能更容易被抓取.2.重要的内容html代码放在前面,放在左边.搜索引擎爬虫是从左往右,从上到下进行抓 ...

  2. react native 初识生命周期

    关于生命周期这块,我是看到慕课堂的一个视频,觉得将的很好,引入很容易理解,地址是:https://www.imooc.com/video/14288  如果你们想了解一下,也可以去看看 RN 组件的生 ...

  3. sqli-labs(六)

    第十一关: 这关是一个登陆口,也是一个sql注入的漏洞,也就是常说的万能密码. 在输入框账号密码种分别输入 1'  和1'  页面会报错. 后台使用的单引符号进行的拼接.账号输入1' or '1'=' ...

  4. HDU 1568 Fibonacci(大数前4位)

    转载自:http://blog.csdn.net/thearcticocean/article/details/47615241 分析:x=1234567.求其前四位数: log10(x)=log10 ...

  5. 准备MyBatis

    MyBatis下载:https://github.com/mybatis/mybatis-3/releases MyBatis文件目录: 中文参考文档:http://www.mybatis.org/m ...

  6. Java多线程-----实现生产者消费者模式的几种方式

       1 生产者消费者模式概述 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题.生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理 ...

  7. 启动与关闭WebService

    [1]代码 /* * @brief: 启动WebServcie服务器 * @return:void */ void UPCSoftphoneClient::startWebService() { m_ ...

  8. Linux Firewall 开启与关闭 以及sudo 设置

    Linux 系统下,普通用户经常需要使用root 用户的权限,所以要经常切换到root用户,比较麻烦,因此可以给普通用户添加root 权限,需要在常规命令前面加上sudo 切换到root vi  /e ...

  9. Java并发编程:volatile关键字解析-转

    Java并发编程:volatile关键字解析 转自海子:https://www.cnblogs.com/dayanjing/p/9954562.html volatile这个关键字可能很多朋友都听说过 ...

  10. Jmeter分布式压力测试

    有时候,一台机器无法支持很多个虚拟用户并发,这时就会使用分布式测试来实现这个功能,jmeter是有提供这个功能的.要实现分布式测试,得在主从(agent和controler)机器的jmeter安装目录 ...