浅谈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. 前端基础进阶(五):全方位解读this

    https://segmentfault.com/a/1190000012646488  https://yangbo5207.github.io/wutongluo/ 说明:此处只是记录阅读前端基础 ...

  2. pyCharm编辑器激活使用

    1.打开激活窗口 2.选择 Activate new license with License server (用license server 激活) 3.在 License sever addres ...

  3. AIX挂载NFS写入效率低效解决

    背景: Linux是NFS的Server端,AIX是NFS的Client端(此外,有一个Linux也作为Client端对比测试). 1.NFS对应的底层设备是闪存卡,本地测试I/O写性能可达2GB/s ...

  4. end to end

    深度学习中的end to end是什么意思? 端到端就是输入一个数据进入模型,然后模型直接可以输出你想要的结果,也就是一体性. 简单讲就是,Input--->系统(这里指神经网络)---> ...

  5. LeetCode11.盛最多水的容器

    给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) .在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0).找出其中的两条线, ...

  6. C#会对于未赋值的变量/成员变量,给予一个初始值吗?

    如果我有程序如下: C# code   ? 1 2 3 4 5 6 7     public class My     {         public bool b;         public  ...

  7. c# Mongodb创建自增列

    MongoCollection<BsonDocument> tblCount; if (!db.CollectionExists(tblCountName))            {   ...

  8. hbase-java-api002(flush)

    package api; import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apa ...

  9. 学习笔记<1>技术体系结构

    Android的系统架构采用了分层架构的思想,如上图所示.从上层到底层共包括四层,分别是   1.应用程序程序层   2.应用框架层   3.系统库和Android运行时 4.Linux内核.   每 ...

  10. GitHub 代码上传

    方法一 登录GitHub后,点击下面的图 New responsitory 按钮 或者点击绿色按钮 New repository,新建一个新建一个远程仓库(remote repository),点击后 ...