2.1 包装类的作用
(1)基本数据类型的存在意义
我们都知道在Java语言中,new一个对象存储在堆里,我们通过栈中的引用来使用这些对象。但是对于经常用到的一系列类型如int、boolean…如果我们用new将其存储在堆里就不是很高效——特别是简单的小的变量。所以,同C++一样Java也采用了相似的做法,决定基本数据类型不是用new关键字来创建,而是直接将变量的值存储在栈中,方法执行时创建,结束时销毁,因此更加高效。所以基本数据类型的存在意义是为了运行效率;
(2)为什么要引入包装类
Java是一门面向对象的编程语言,但是Java中的基本数据类型却不是面向对象的,并不具有对象的性质,这在实际生活中存在很多的不便。为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得具有了面向对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作,方便涉及到对象的操作。所以,引入包装类是为了方便对基本类型进行操作;
在使用集合类型时,就一定要使用包装类型,因为容器都是装object的,基本数据类型显然不适用。
2.2 包装类的原理
2.2.1 包装类
Java设计当初就提供类8种基本数据类型及对应的8 种包装类(封装类);
基本数据类型 | 包装类 |
---|---|
byte | Byte |
boolean | Boolean |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
![](https://user-images.githubusercontent.com/33022636/57301367-6f749100-710b-11e9-869e-914f9acdaed2.jpg)
2.2.2 装箱和拆箱
下面均以int,Integer为例进行讲解;
- 装箱:基本数据类型转换为包装类;装箱有两种方法:
//装箱两种方法
Integer i1 = new Integer(8);
Integer i2 = Integer.valueOf(8);
// 自动装箱,会隐式调用valueOf方法
Integer i3 = 8;
fun(8);
void fun( Integer a){
//...
}
- 拆箱:包装类转换为基本数据类型;拆箱方法:
//拆箱方法
int i5 = i3.intValue();
//自动拆箱,会隐式调用intValue方法
int i4 = i3;
test(i3);
void test(int a){
//...
}
2.2.3 缓存池
上一节说到,装箱有两种方法:new Integer(8)
和 Integer.valueOf(8)
;他们的区别在于:
new Integer(8)
每次调用创建一个新对象;Integer.valueOf(8)
使用缓存池中的对象,多次调用会取得同一个对象的引用(可复用)。
下面看一下Integer的源码,分析一下valueOf和缓存池;
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];//1
return new Integer(i);//2
}
Integer i6 = 127;//自动装箱,值在缓存池范围内,调用valueOf,执行1,i6是缓存池单元的引用(地址);
Integer i7 = 128;//自动装箱,值在缓存池范围外,调用valueOf,执行2,最终调用newInteger,i7是堆单元的引用;
在 Java 8 中,Integer 缓存池的大小默认为 -128~127,和byte范围相同;
private static class IntegerCache {
static final int low = -128;//Integer缓存池最小值
static final int high;//Integer缓存池最大值
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;//Integer缓存池最大值
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;//Integer缓存池最大值
cache = new Integer[(high - low) + 1];
int j = low;
//缓存池的初始化
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
2.3.4 封装类对象的比较
封装类对象的比较强制使用equals(源自阿里编码规范);
public class Test {
public static void main(String[] args) {
Integer i1 = new Integer(127);
Integer i2 = Integer.valueOf(127);
Integer i3 = 127;
Integer i4 = 127;
Integer i5 = 128;
Integer i6 = 128;
int i7 = 127;
System.Out.println(i1 == i2); //1false;
System.Out.println(i2 == i3); //2true
System.Out.println(i3 == i4); //3true
System.Out.println(i5 == i6); //4false
System.Out.println(i5.equals(i6)); //5true
System.Out.println(i1 == i7);//true
System.Out.println(i3 == i7);//true
}
}
首先声明==和equals的区别:
- equals比较对象的内容(所有属性值);
- ==如果是基本数据类型比较值,如果是对象比较内存地址;
(1)i1是堆中新开辟一块内存的地址;i2是堆中缓存池某一单元的地址;1输出false;
(2)自动装箱,值在缓存池范围内,调用valueOf,i2、i3和i4是同一缓存池单元的引用(地址);2,3输出true;
(3)自动装箱,值在缓存池范围外,valueOf最终调用newInteger,i5,i6都创建了一个新对象,俩对象内容相同,地址不同;4false ,5true ;
(3) System.Out.println(i1 == i7);
编译成 System.Out.println(i1.intValue() == i7);
所以输出true;System.Out.println(i3 == i7);
同理输出true;
2.2.5 String和Integer相互转换
- String to Integer:
Intrger.parseInt(string);
- Integer to String:
Integer.toString();
2.3 基本类型和包装类的区别
-
存储位置:基本类型变量值存储在栈中,而包装类型是将对象放存储在堆中,然后通过引用来使用;
-
默认初值:基本类型的默认初始如int为0,boolean为false,而包装类型的默认初始为null;
-
作用不同:基本数据类型的存在意义是为了运行效率;而引入包装类是为了方便对基本类型进行操作;
2.3 包装类使用场景
- 泛型(包括容器的泛型参数)只能使用包装类;
- 某个允许为null的属性只能使用包装类;
以下三条源自阿里编码规范:
- 所有POJO类(简单Java类,只有属性、构造器、seter、geter的类)属性;
- RPC(远程方法调用)方法返回值和参数必须使用包装数据类型;
- 推荐所有的局部变量使用基本数据类型,避免频繁GC;
ps:在一个ORM中存在一个Student类和Student类表,有一个JavaBean 类Student类中有一个表示学生考试分数的属性score,相对应Student类表中有一个表示学生考试分数的字段score;如果用Integer而不用int定义这个属性,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样
2.4 包装类存在的问题
- 效率低;
由于使用包装类会创建新对象,而且若在循环中使用包装类会频繁GC,效率比较低;所以在循环中尽量使用基本数据类型;所以阿里编码规范推荐所有的局部变量使用基本数据类型;
Integer sum = 0;
for(int i=0; i<1000; i++){
sum+=i;
}
- 拆箱可能会产生空指针异常;
Object obj = null;//1
int i = (Integer)obj;//2
2处自动拆箱会调用obj.intValue();
方法,从而产生空指针异常;
参考: