zoukankan      html  css  js  c++  java
  • 详解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 @Override
    2 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);  //true
    10         System.out.println(i3==i4);  //false
    11     }
    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); //false
    10         System.out.println(i3==i4); //false
    11     }
    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);//true
    10         System.out.println(i3==i4);//true
    11     }
    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 @Override
    2 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);  //200
    5 System.out.println(num3 == (num1 + num2));  //true
    6 System.out.println(num3.equals(num1 + num2));  //false

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

    1 @Override
    2 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、当两种不同类型用==比较时,包装器类的需要拆箱, 当同种类型用==比较时,会自动拆箱或者装箱

  • 相关阅读:
    codevs 1115 开心的金明
    POJ 1125 Stockbroker Grapevine
    POJ 2421 constructing roads
    codevs 1390 回文平方数 USACO
    codevs 1131 统计单词数 2011年NOIP全国联赛普及组
    codevs 1313 质因数分解
    洛谷 绕钉子的长绳子
    洛谷 P1276 校门外的树(增强版)
    codevs 2627 村村通
    codevs 1191 数轴染色
  • 原文地址:https://www.cnblogs.com/wang-yaz/p/8516151.html
Copyright © 2011-2022 走看看