为什么要有装箱和拆箱?
有些情况下基本类型无法实现对应功能。
有哪些场景不得不使用包装类?
- 集合类只能使用包装类型,不允许使用基本类型,所以需要有包装类
- 由于基本类型没有null的状态,所以需要有包装类对象能够表示null
- 泛型只针对类,不能使用基本类型
- 反射也只能反射类,不能反射基本类型
- 包装类一旦创建,是值不可变的对象,不容易赋值出错
为什么Java还要定义基本数据类型,直接使用包装类完全面向对象不就行了吗?
这需要从Java虚拟机的角度解释性能问题,当执行一个方法中的算术运算时,虚拟机是基于栈帧中的操作数栈进行数据的读取和运算。
如果参与运算的是基本数据类型,由于基本类型大小是固定的,一来本身占用空间小,二来基本类型的变量值可以直接存入局部变量表,直接从局部变量表中读取数据速度很快(跟访问对象的方式相比而言);基本也不需要垃圾回收,局部变量表是位于栈帧中的,方法执行完毕,栈帧出栈,基本数据类型的变量都会自动释放内存。
如果参与运算的是包装类对象,对象创建后是位于堆中,对象在局部变量表中存储的只是对象指针,指向其在堆中的位置,运算时先访问局部变量表,再访问堆中的对象内部的值,比直接访问局部变量表要多一次额外的访问;其次创建对象占用了额外的空间,因为对象比基本类型要多耗费额外的空间,如对象头等;再次对象创建后还需要垃圾回收,增加了垃圾回收的负担。
基本类型在方法传参时是值的拷贝传递,对象类型在方法传参是引用传递。
自动装箱和自动拆箱是怎么回事?实际发生了什么?
如下的Java代码:
1 | Integer a = 10; |
在Intellij idea里通过View - Show Bytecode查看字节码:
1 | L0 |
可以看到int对象的自动装箱,是调用了Integer.valueOf()方法,自动拆卸是调用了Integer.intValue()。
触发自动装箱和自动拆箱的场景有哪些?
自动装箱的场景:
1. 基本类型赋值给包装类型,如 Integer a = 10;
自动拆箱的场景:
1. 包装类型赋值给基本类型
2. 参与表达式运算或算数运算
进行 =
赋值操作(装箱或拆箱)
进行+
,-
,*
,/
混合运算 (拆箱)
进行>
, <
, ==
比较运算(拆箱)
当 ==
运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象。
而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)调用equals进行比较(装箱)。
ArrayList,HashMap等集合类 添加基础类型数据时,因为集合只能添加引用类型(装箱)。
Kotlin已经废弃了基本数据类型,已经全是面向对象了,Kotlin这样做没有性能问题吗?
Kotlin只是在语法层面屏蔽了内部的实现细节,在编译时,还是会把Kotlin的基本数据类型对应的对象类型还是换转为Java基本数据类型,这可以通过反编译Kotlin为Java后看出,这样Kotlin更加彻底面向对象,更加的函数式
Kotiln中整数数组为什么不直接使用 Array 而是 IntArray ?
IntArray最终会编译成int[]。
Array
可以通过查看字节码来验证这一点。
包装类型遍历时会有自动装箱和拆箱的过程,会创建大量的临时对象,加重了垃圾回收的负担,垃圾回收多了会引发stop the world,进而会增加卡顿的几率。
自动装箱和拆箱会产生什么问题?
在自动拆卸时,当包装类变量为null时,会发生空指针异常,例如
Integer a = null;
int b = a;
第二句赋值时会调用a.intValue(),然而a是null,会抛出空指针异常。
自动装箱会创建包装类对象,如果在大的循环中有自动装箱的发生,会创建很多临时的无用的包装类对象,增加了垃圾回收的负担,垃圾回收会引发stop the world停止所有的线程工作,导致系统的吞吐量降低。