详解 Java 中的自动装箱与拆箱,5000+字,看了不懂你打我!

什么是自动装箱拆箱?

很简单,下面两句代码就可以看到装箱和拆箱过程


//自动装箱 

Integer total = 99 ;
//自动拆箱
int totalprim = total ;

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

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


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

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

1 javap -c StringTest

//java学习交流:737251827 进入可领取学习资源及对十年开发经验大佬提问,免费解答!

Integer total = 99;

执行上面那句代码的时候,系统为我们执行了:

Integer total = Integer.valueOf(99);

int totalprim = total;

执行上面那句代码的时候,系统为我们执行了:

int totalprim = total.intValue();

我们现在就以 Integer 为例,来分析一下它的源码:

1、首先来看看 Integer.valueOf 函数


public 
class 
Main 
{

  public static void main ( String [ ] args ) {
    //自动装箱
   Integer total = 99 ;
    //自定拆箱
   int totalprim = total ;
  }
}

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


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


private final int value
; 
public 
Integer
(
int value
) 
{

  this .value = value ;
}
public Integer (String string ) throws NumberFormatException {
  this ( parseInt (string ) ) ;
}

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

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

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

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

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

2、接着看看 intValue 函数

@Override

public int intValue ( ) {
  return value ;
}

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

相关问题

上面我们看到在 Integer 的构造函数中,它分两种情况:

i 
>= 
128 
|| i 
< 
-
128 
===
==
> 
new 
Integer
(i
) 

i < 128 && i >= - 128 === == > SMALL_VALUES [i + 128 ]


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

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

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


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 ) ;
    //true
   System .out . println (i3 ==i4 ) ;
    //false
  }
}

代码的后面,我们可以看到它们的执行结果是不一样的,为什么,在看看我们上面的说明。

1、i1和i2会进行自动装箱,执行了valueOf函数,它们的值在(-128,128]这个范围内,它们会拿到 SMALL_VALUES数组里面的同一个对象 SMALL_VALUES[228],它们引用到了同一个 Integer 对象,所以它们肯定是相等的。

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

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


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 ) ;
    //false
   System .out . println (i3 ==i4 ) ;
    //false
  }
}

看看上面的执行结果,跟 Integer 不一样,这样也不必奇怪,因为它们的 valueOf 实现不一样,结果肯定不一样,那为什么它们不统一一下呢?最新面试题整理好了,点击 Java面试库小程序在线刷题。

这个很好理解,因为对于 Integer,在(-128,128]之间只有固定的 256 个值,所以为了避免多次创建对象,我们事先就创建好一个大小为 256 的 Integer 数组SMALL_VALUES,所以如果值在这个范围内,就可以直接返回我们事先创建好的对象就可以了。

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

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


public 
static Double 
valueOf
(
double d
) 
{

  return new Double (d ) ;
}

下面我们进行一个归类:

Integer 派别:Integer、Short、Byte、Character、Long 这几个类的 valueOf 方法的实现是类似的。

Double 派别:Double、Float 的 valueOf 方法的实现是类似的。每次都返回不同的对象。


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

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


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 ) ;
    //true
   System .out . println (i3 ==i4 ) ;
    //true
  }
}

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


public 
static Boolean 
valueOf
(
boolean b
) 
{

  return b ? Boolean . TRUE : Boolean . FALSE ;
}

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


public 
static final Boolean 
TRUE 
= 
new 
Boolean
(
true
)
;

public static final Boolean FALSE = new Boolean ( false ) ;

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

Integer num1 
= 
400
;

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

我们先来看看equals源码:

@Override 
public boolean 
equals
(
Object o
) 
{

  return (o instanceof Integer ) && ( ( (Integer ) o ) .value == value ) ;
}

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

Integer num1 
= 
100
;

int num2 = 100 ;
Long num3 = 200l ;
System .out . println (num1 + num2 ) ; //200
System .out . println (num3 == (num1 + num2 ) ) ; //true
System .out . println (num3 . equals (num1 + num2 ) ) ; //false

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

@Override 
public boolean 
equals
(
Object o
) 
{

  return (o instanceof Long ) && ( ( (Long ) o ) .value == value ) ;
}

它必须满足两个条件才为true:

1、类型相同

2、内容相同

上面返回 false 的原因就是类型不同。

Integer num1 
= 
100
;

Ingeger num2 = 200 ;
Long num3 = 300l ;
System .out . println (num3 == (num1 + num2 ) ) ; //true

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

//java学习交流:737251827 进入可领取学习资源及对十年开发经验大佬提问,免费解答!

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

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

int num1 
= 
100
; int num2 
= 
200
;

long mum3 = 300 ;
System .out . println (num3 == (num1 + num2 ) ) ; //true

就说明了为什么最上面会返回 true.最新面试题整理好了,点击 Java面试库小程序在线刷题。

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

陷阱1:

Integer integer100
=
null
;

int int100 =integer100 ;

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

总结

1、需要知道什么时候会引发装箱和拆箱

2、装箱操作会创建对象,频繁的装箱操作会消耗许多内存,影响性能,所以可以避免装箱的时候应该尽量避免。

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

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



请使用浏览器的分享功能分享到微信等