PS:三天后朱健迎来他的第一场面试,希望他能得到他想要的结果。好了不说他了,我们干我们的。
什么是装箱类?我们为什么要装箱?
首先我们知道,JAVA存在8中基本类型,4中引用类型。
byte short int long
float double
char boolean
StrongReference SoftReference
WeakReference PhantomReference
对象
(也就是引用数据类型)相对于基本数据类型更加高级,它不但可以存储数据还可以存储方法还可以继承还可以封装………那强调万物皆对象的JAVA,为什么还需要int,float这些基本类型呢。下面这段话是在论坛看到的,说得真的很好,插一下。
按理说C#被设计成一种完全面向对象的语言。因此,包括数字、字符、日期、布尔值等等在内的一切,都是对象。似乎只需要一种方式来对待这些对象就可以了。
但是C#不是只停留在学院中和理想中,它必须为性能而妥协,我们知道,对于CPU来说,处理一个完整的对象,需要很多的指令,对于内存来说,又需要很多的内存。如果连整数都是对象,那么性能自然很低。C#于是使用了一种机制,使得这些基本类型在一般的编程中被当作非对象的简单类型处理,在另一些场合,又允许它们被视作是一个对象。这种机制就是装箱和拆箱。
装箱后的对象看上去和一个对象一样,拥有方法,可以当作object处理,拆箱后的变量,看上去又如同C语言中的那些变量、结构体一样,可以直接参与运算和处理。
JAVA也是一样的,一句话概括装箱
和拆箱
自动根据数值类型创建对应的对象,此为装箱(boxing)
自动根据装箱(类型)转变为基本类型,此为拆箱(unBoxing)
基本类型 --- 装箱类型
int --- Integer
short --- Short
long --- Long
double --- Double
float --- Float
boolean --- Boolean
byte --- Byte
char --- Character
如何编写装箱类型,它怎么用的。
Integer i = 1; //自动装箱
int j = i; //自动拆箱
public void argAutoBoxing(Integer i) {
}
argAutoBoxing(1); //自动装箱
public void argAutoUnBoxing(int i) {
}
argAutoUnBoxing(new Integer(1)); //自动拆箱
//我们将只有存储作用的基本类型变为装箱类型的对象后,可以使用背后的`方法`相互转化
//这个用法非常常见,也是主要功能
String year = "2018";
int i = Integer.parseInt(year);
System.out.println(i/2);
注意!Integer i = new Integer(xx)
和Integer i = xx
是不一样的,这个后面再提及
什么时候使用int
,什么时候用Integer
?
我们知道的是,在堆内存创建对象的消耗比起使用栈内存是要大的 ,如果是在数据量较大或是循环次数较多的情况下,对性能的影响是非常大的。若非是特殊需求,一般使用基本类型而不是装箱类型。
1 |
|
sum为Integer类型,在上面的循环中会创建2000个无用的Integer对象,在这样庞大的循环中,会降低程序的性能并且加重了垃圾回收的工作量。在我们编程时,需要注意到这一点,正确地声明变量类型,避免因为自动装箱引起的性能问题
拆装箱的内部原理是什么?
我们上面提到的自动拆装箱为例1
2
3
4
5
6
Integer i = 1; //自动装箱 JVM调用Integer类的valueOf()方法
int j = i; //自动拆箱 JVM调用Integer类的intValue()方法
//其他类型类推
让我们看看valueOf()
和intValue()
两个方法的具体实现
1 | /** |
通过valueOf()方法创建Integer对象的时候,如果数值在符合条件,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
1 |
|
通过intValue()方法将参数转化为基本类型并返回。
练习&拓展
下面是搜集的一些关于装箱类的一些基础面试题,让我们通过这些试题来练习一下也拓展之前并没有提及的知识点。
友情提示
- 对于两边都是包装类型的比较==比较的是引用,equals比较的是值。
- 对于两边有一边是表达式(包含算数运算)则==比较的是数值(自动触发拆箱过程),对于包装类型equals方法不会进行类型转换。
1.
1 |
|
这里得到的输出应是 true 和 false ,
通过观察上面提到的装箱发放valueOf()方法,并再仔细观看里面嵌入的另一个关键方法IntegerCache()我们发现,可以进一步将进一步解读ValueOf()为
valueOf方法,参数如果是-128~127之间的值会直接返回内部缓存池中已经存在对象的引用,参数是其他范围值则返回新建对象
2.
1 |
|
我们会发现得到的输出都是false,是因为不同的数据类型有不同的valueOf()实现方式。
通过查阅文档和编写实验,可以得出一个结论:
对于Integer、Short、Byte、Character、Long类型的valueOf方法,参数如果是-128~127之间的值会直接返回内部缓存池中已经存在对象的引用,参数是其他范围值则返回新建对象;
而Double、Float类型与Integer类型类似,一样会调用Double、Float的valueOf方法,但是不管传入的参数值是多少都会new一个对象来表达该数值,因为在某个范围内的整型数值的个数是有限的,而浮点数却不是。
3.
1 | Boolean i = null; |
这里,会得到一个NullPointerException异常
。虽然自动拆装箱非常方便,但千万不要同等对待了。 对象Boolean里有 true
false
null
,基本类型boolean里可没有null
。
4.
1 | public static void main(String[] args) { |
照理来说,通过练习1我们应该推测这里得到的输出应该是true,12并没有超出缓存范围。但通过实践得到的输出确是false。这是为什么呢?
因为,new Integer(xxx)这种创建对象的方法不是自动装箱,没有用到cache。两个对象自然指向的不是一个区域的值。
5.
1 |
|
这个题我就不讲解,结合前面的知识点来练习就可以得到相应的正确输出,也可以真正了解到==
和equals
的区别。
- 装箱类的应用十分广泛,基础的知识掌握是必须的。
- 总结,是一件快乐的事。
- 下周循环!