java 什么是自动装箱拆箱_详解 Java 的自动装箱与拆箱 (Autoboxing and unboxing)

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

一什么是自动装箱拆箱

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

Integertotal=99;

// 自定拆箱

inttotalprim=total;

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

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

ab7653affab982b574eb7acc55df2e04.gif

ab7653affab982b574eb7acc55df2e04.gif

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

publicstaticvoidmain(String[]args){

// 自动装箱

Integertotal=99;

// 自定拆箱

inttotalprim=total;

}

}

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

1 javap -c StringTest

ab7653affab982b574eb7acc55df2e04.gif

Integer total = 99;

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

inttotalprim=total;

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

int totalprim = total.intValue();

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

1 首先来看看 Integer.valueOf 函数publicstaticIntegervalueOf(inti){

returni>=128||i

}

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

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

publicInteger(intvalue){

this.value=value;

}

publicInteger(Stringstring)throwsNumberFormatException{

this(parseInt(string));

}

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

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

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

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

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

2 接着看看 intValue 函数@Override

publicintintValue(){

returnvalue;

}

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

二相关问题

上面我们看到在 Integer 的构造函数中, 它分两种情况:1i>=128||inewInteger(i)

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

1privatestaticfinalInteger[]SMALL_VALUES=newInteger[256];

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

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

publicstaticvoidmain(String[]args){

Integeri1=100;

Integeri2=100;

Integeri3=200;

Integeri4=200;

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

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

}

}

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

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

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

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

publicstaticvoidmain(String[]args){

Doublei1=100.0;

Doublei2=100.0;

Doublei3=200.0;

Doublei4=200.0;

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

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

}

}

看看上面的执行结果, 跟 Integer 不一样, 这样也不必奇怪, 因为它们的 valueOf 实现不一样, 结果肯定不一样, 那为什么它们不统一一下呢?

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

但是对于 Double 类型来说, 我们就不能这样做, 因为它在这个范围内个数是无限的

总结一句就是: 在某个范围内的整型数值的个数是有限的, 而浮点数却不是

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

returnnewDouble(d);

}

下面我们进行一个归类:

Integer 派别: IntegerShortByteCharacterLong 这几个类的 valueOf 方法的实现是类似的

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

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

ab7653affab982b574eb7acc55df2e04.gif

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

publicstaticvoidmain(String[]args){

Booleani1=false;

Booleani2=false;

Booleani3=true;

Booleani4=true;

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

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

}

}

可以看到返回的都是 true, 也就是它们执行 valueOf 返回的都是相同的对象publicstaticBooleanvalueOf(booleanb){

returnb?Boolean.TRUE:Boolean.FALSE;

}

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

publicstaticfinalBooleanFALSE=newBoolean(false);

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

intnum2=400;

System.out.println(num1==num2);//true

说明 num1 == num2 进行了拆箱操作Integernum1=100;

intnum2=100;

System.out.println(num1.equals(num2));//true

我们先来看看 equals 源码:@Override

publicbooleanequals(Objecto){

return(oinstanceofInteger)&&(((Integer)o).value==value);

}

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

intnum2=100;

Longnum3=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

publicbooleanequals(Objecto){

return(oinstanceofLong)&&(((Long)o).value==value);

}

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

1 类型相同

2 内容相同

上面返回 false 的原因就是类型不同Integernum1=100;

Ingegernum2=200;

Longnum3=300l;

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

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

ab7653affab982b574eb7acc55df2e04.gif

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

我们来对基础类型进行一个测试:intnum1=100;

intnum2=200;

longmum3=300;

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

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

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

陷阱 1:Integerinteger100=null;

intint100=integer100;

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

总结:

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

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

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

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

来源: https://www.cnblogs.com/wang-yaz/p/8516151.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值