详解Java的自动装箱与拆箱(Autoboxing and unboxing)

详解Java的自动装箱与拆箱(Autoboxing and unboxing)

一、什么是自动装箱拆箱 
很简单,下面两句代码就可以看到装箱和拆箱过程

1 //自动装箱2 Integer total = 99;3 4 //自动拆箱5 int totalprim = total;

简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

下面我们来看看需要装箱拆箱的类型有哪些:

这个过程是自动执行的,那么我们需要看看它的执行过程:

1 public class Main {2     public static void main(String[] args) {3     //自动装箱4     Integer total = 99;5 6     //自定拆箱7     int totalprim = total;8     }9 }

反编译class文件之后得到如下内容:

1 javap -c StringTest

Integer total = 99; 
执行上面那句代码的时候,系统为我们执行了: 
Integer total = Integer.valueOf(99);

int totalprim = total; 
执行上面那句代码的时候,系统为我们执行了: 
int totalprim = total.intValue();

我们现在就以Integer为例,来分析一下它的源码: 
1、首先来看看Integer.valueOf函数

1 public static Integer valueOf(int i) {2 return  i >= 128 || i < -128 ? new Integer(i) : SMALL_VALUES[i + 128];3 }

它会首先判断i的大小:如果i小于-128或者大于等于128,就创建一个Integer对象,否则执行SMALL_VALUES[i + 128]。

首先我们来看看Integer的构造函数:

1 private final int value;2 3 public Integer(int value) {4     this.value = value;5 }6 7 public Integer(String string) throws NumberFormatException {8     this(parseInt(string));9 }

它里面定义了一个value变量,创建一个Integer对象,就会给这个变量初始化。第二个传入的是一个String变量,它会先把它转换成一个int值,然后进行初始化。

下面看看SMALL_VALUES[i + 128]是什么东西:

1 private static final Integer[] SMALL_VALUES = new Integer[256];

它是一个静态的Integer数组对象,也就是说最终valueOf返回的都是一个Integer对象。

所以我们这里可以总结一点:装箱的过程会创建对应的对象,这个会消耗内存,所以装箱的过程会增加内存的消耗,影响性能。

2、接着看看intValue函数

1 @Override2 public int intValue() {3     return value;4 }

这个很简单,直接返回value值即可。

二、相关问题 
上面我们看到在Integer的构造函数中,它分两种情况:

1、i >= 128 || i < -128 =====> new Integer(i) 
2、i < 128 && i >= -128 =====> SMALL_VALUES[i + 128]

1 private static final Integer[] SMALL_VALUES = new Integer[256];

SMALL_VALUES本来已经被创建好,也就是说在i >= 128 || i < -128是会创建不同的对象,在i < 128 && i >= -128会根据i的值返回已经创建好的指定的对象。

说这些可能还不是很明白,下面我们来举个例子吧:

1 public class Main { 2     public static void main(String[] args) { 3  4         Integer i1 = 100; 5         Integer i2 = 100; 6         Integer i3 = 200; 7         Integer i4 = 200; 8  9         System.out.println(i1==i2);  //true10         System.out.println(i3==i4);  //false11     }12 }

代码的后面,我们可以看到它们的执行结果是不一样的,为什么,在看看我们上面的说明。 
1、i1和i2会进行自动装箱,执行了valueOf函数,它们的值在(-128,128]这个范围内,它们会拿到SMALL_VALUES数组里面的同一个对象SMALL_VALUES[228],它们引用到了同一个Integer对象,所以它们肯定是相等的。

2、i3和i4也会进行自动装箱,执行了valueOf函数,它们的值大于128,所以会执行new Integer(200),也就是说它们会分别创建两个不同的对象,所以它们肯定不等。

下面我们来看看另外一个例子:

1 public class Main { 2     public static void main(String[] args) { 3  4         Double i1 = 100.0; 5         Double i2 = 100.0; 6         Double i3 = 200.0; 7         Double i4 = 200.0; 8  9         System.out.println(i1==i2); //false10         System.out.println(i3==i4); //false11     }12 }

看看上面的执行结果,跟Integer不一样,这样也不必奇怪,因为它们的valueOf实现不一样,结果肯定不一样,那为什么它们不统一一下呢? 
这个很好理解,因为对于Integer,在(-128,128]之间只有固定的256个值,所以为了避免多次创建对象,我们事先就创建好一个大小为256的Integer数组SMALL_VALUES,所以如果值在这个范围内,就可以直接返回我们事先创建好的对象就可以了。

但是对于Double类型来说,我们就不能这样做,因为它在这个范围内个数是无限的。 
总结一句就是:在某个范围内的整型数值的个数是有限的,而浮点数却不是。

所以在Double里面的做法很直接,就是直接创建一个对象,所以每次创建的对象都不一样。

1 public static Double valueOf(double d) {2     return new Double(d);3 }

下面我们进行一个归类: 
Integer派别:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。 
Double派别:Double、Float的valueOf方法的实现是类似的。每次都返回不同的对象。

下面对Integer派别进行一个总结,如下图:

下面我们来看看另外一种情况:

1 public class Main { 2     public static void main(String[] args) { 3  4         Boolean i1 = false; 5         Boolean i2 = false; 6         Boolean i3 = true; 7         Boolean i4 = true; 8  9         System.out.println(i1==i2);//true10         System.out.println(i3==i4);//true11     }12 }

可以看到返回的都是true,也就是它们执行valueOf返回的都是相同的对象。

1 public static Boolean valueOf(boolean b) {2     return b ? Boolean.TRUE : Boolean.FALSE;3 }

可以看到它并没有创建对象,因为在内部已经提前创建好两个对象,因为它只有两种情况,这样也是为了避免重复创建太多的对象。

1 public static final Boolean TRUE = new Boolean(true);2 3 public static final Boolean FALSE = new Boolean(false);

上面把几种情况都介绍到了,下面来进一步讨论其他情况。

1 Integer num1 = 400;  2 int num2 = 400;  3 System.out.println(num1 == num2); //true
说明num1 == num2进行了拆箱操作
1 Integer num1 = 100;  2 int num2 = 100;  3 System.out.println(num1.equals(num2));  //true

我们先来看看equals源码:

1 @Override2 public boolean equals(Object o) {3     return (o instanceof Integer) && (((Integer) o).value == value);4 }

我们指定equal比较的是内容本身,并且我们也可以看到equal的参数是一个Object对象,我们传入的是一个int类型,所以首先会进行装箱,然后比较,之所以返回true,是由于它比较的是对象里面的value值。

1 Integer num1 = 100;  2 int num2 = 100;  3 Long num3 = 200l;  4 System.out.println(num1 + num2);  //2005 System.out.println(num3 == (num1 + num2));  //true6 System.out.println(num3.equals(num1 + num2));  //false

1、当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。 
2、对于num3.equals(num1 + num2)为false的原因很简单,我们还是根据代码实现来说明:

1 @Override2 public boolean equals(Object o) {3     return (o instanceof Long) && (((Long) o).value == value);4 }

它必须满足两个条件才为true: 
1、类型相同 
2、内容相同 
上面返回false的原因就是类型不同。

1 Integer num1 = 100;2 Ingeger num2 = 200;3 Long num3 = 300l;4 System.out.println(num3 == (num1 + num2)); //true

我们来反编译一些这个class文件:javap -c StringTest

可以看到运算的时候首先对num3进行拆箱(执行num3的longValue得到基础类型为long的值300),然后对num1和mum2进行拆箱(分别执行了num1和num2的intValue得到基础类型为int的值100和200),然后进行相关的基础运算。

我们来对基础类型进行一个测试:

1 int num1 = 100;2 int num2 = 200;3 long mum3 = 300;4 System.out.println(num3 == (num1 + num2)); //true

就说明了为什么最上面会返回true.

所以,当 “==”运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。

陷阱1:

1  Integer integer100=null;  2  int int100=integer100;

这两行代码是完全合法的,完全能够通过编译的,但是在运行时,就会抛出空指针异常。其中,integer100为Integer类型的对象,它当然可以指向null。但在第二行时,就会对integer100进行拆箱,也就是对一个null对象执行intValue()方法,当然会抛出空指针异常。所以,有拆箱操作时一定要特别注意封装类对象是否为null。

总结: 
1、需要知道什么时候会引发装箱和拆箱 
2、装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。

3、equals(Object o) 因为原equals方法中的参数类型是封装类型,所传入的参数类型(a)是原始数据类型,所以会自动对其装箱,反之,会对其进行拆箱

4、当两种不同类型用==比较时,包装器类的需要拆箱, 当同种类型用==比较时,会自动拆箱或者装箱

hello world!!!
(0)

相关推荐

  • 吊打面试官系列:说说Integer缓存范围

    回复"000"获取大量电子书 本文主要大致思路为: 不管从工作中还是面试,这篇文章都应该好好看完,本人认为是非常有用的. 案例 Integer是基本类型int的封装类.平时不管是入 ...

  • Java学习—125.2048代码

    本篇帖的是部分动作代码. 每个方法前都有注释说明本方法的功能. //随机选取一个位置,并填入数字2.如果此位置中已有数字,那么换一个位置. public void rule(){ x=ran.next ...

  • 3W 字详解 Java 集合

    开源前哨 93篇原创内容 公众号 数据结构作为每一个开发者不可回避的问题,而 Java 对于不同的数据结构提供了非常成熟的实现,这一个又一个实现既是面试中的难点,也是工作中必不可少的工具,在此,笔者经 ...

  • 规范详解:防盗窗的破拆(火场求生:技能是怎样炼成的)

    ♚ 语音 ♚ 文字 大家好,今天给大家介绍一本我无意中接触到的书. 书名叫做<火场求生:技能是怎样炼成的>,在2020年8月由应急管理出版社出版. 由于我本人没有从事过灭火救援,消防宣传, ...

  • 详解JAVA面向对象的设计模式 (七)、装饰模式

    装饰模式 Decorator 装饰模式比较简单,我就不单独写实现例子了.参考设计图去实现不是什么问题.建议可以写一写找找感觉. 在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修.相 ...

  • 详解JAVA面向对象的设计模式 (四)、外观模式

    外观模式 Facade 外观模式内容相对简单,就不写新的例子了.本篇文章摘录自 http://c.biancheng.net/view/1369.html 外观模式的定义与特点 外观(Facade)模 ...

  • 详解JAVA面向对象的设计模式 (二)、策略模式

    策略模式 Strategy 介绍 在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改.这种类型的设计模式属于行为型模式. 在策略模式中,我们创建表示各种策略的对象和 ...

  • 详解JAVA面向对象的设计模式 (一)、单例模式

    本系列,记录了我深刻学习设计模式的过程.也算是JAVA进阶学习的一个重要知识点吧. 与设计相关的代码会贴出,但是基础功能的代码会快速带过.有任何错误的地方,都欢迎读者评论指正,感谢.冲冲冲! 单例模式 ...

  • 从零开始学Java(九)详解Java中的方法

    方法 1.什么是方法,有什么用? (可以先看一下一个程序如果没有方法,会出现什么问题?) 方法(英语单词:method)是可以完成某个特定功能的并且可以被重复利用的代码片段. 方法的出现,让代码具有了 ...

  • 从零开始学Java(十四)详解Java中的static关键字(上)

    ✔上一篇Java零基础系列文章我们说到了Java种this关键字的使用,今天继续来说说Java中的static关键字,篇幅较多,分为上下两篇更新,这篇文章主要内容: Java中static关键字 Ja ...

  • 从零开始学Java(十四)详解Java中的static关键字(下)

    接上篇Java-static关键字(上),今儿继续写完,这篇文章主要内容如下: Java static静态代码块 Java static静态方法 Java static静态代码块 静态代码块的语法格式 ...