JAVA的包装类解析

目录

  1. 什么是包装类
  2. 自动装箱和自动拆箱
  3. 包装类可以为null,而基本类型不可以
  4. 包装类型可用于泛型,而基本类型不可以
  5. 基本类型比包装类型更高效
  6. 两个包装类型的值可以相同,但却可以不相等

什么是包装类

Java的每个基本类型都有对应的包装类型,比如说int的包装类型是Integer,double的包装类型是Double。

自动装箱和自动拆箱

既然有了基本类型和包装类型,肯定有些是要在他们之间进行转换。把基本类型转换成包装类型的过程叫做装箱。反之,把包装类型转换成基本类型的过程叫做拆箱。

在Java5之前,开发人员要进行手动拆装箱

Integer integer2 = new Integer(10);
int j = integer2.intValue();

java5引入了自动拆装箱的功能,减少了开发人员的工作

Integer chenmo  = 10;  // 自动装箱
int wanger = chenmo; // 自动拆箱

使用反编译工作编译后的结果如下:

Integer integer3 = Integer.valueOf(10);
int k = integer3.intValue();

也就说自动装箱是调用了Integer.valueOf()完成的,自动拆箱是通过调用integer.intValue()完成的。

理解了自动拆装箱的原理后,我们来看一道面试题

int a = 100;
int b = 100;
System.out.println(a ==b);

Integer c = 100;
Integer d = 100;
System.out.println(c == d);

Integer e = 200;
Integer f = 200;
System.out.println(e == f);

System.out.println( a == c);

int h = 200;
System.out.println( e == h);

在看这段代码之前,我们要明白的是 == 号,基础类型比较的是值,引用类型比较的是内存地址

第一段代码,很好理解,基础类型比较的是值

第二段代码是包装类,这里需要引入一个缓冲池(IntegerCache )的概念,JVM把-128到127的数值存到了内存中,需要的时候直接从内存拿,而不是重新创建一个对象

第三段代码也很容易理解,如果-128到127是从缓冲池中拿,那么超过这个范围的,自然就是堆中创建了

第四段是基本类型和包装类型做比较,这时候包装类型会先转成基本类型,然后再比较

第五段代码同上,没有在堆中创建的对象这一步

结果即是:true、true、false、true、true

之前我们就已经知道了自动装箱是Integer.valueOf()方法,我们现在看看它的源码,如果做到-128到127是缓冲池中拿,而超过了则需要在堆中创建。

public static Integer valueOf(int i) {
//如果需要包装的数值大于IntegerCache.low 并且小于IntegerCache.high,就在IntegerCache.cache中拿,否则就new一个Integer,从这里看cache应该是个数组,保存了-128到127的数值
if (i >= IntegerCache.low && i <= IntegerCache.high)
// 数据中的位置是0 - 255 ==> -127 - 128 ==> -IntegerCache.low的位置的值是0
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128; //最小值-128
static final int high;
static final Integer cache[];

//静态代码块,初始化时就加载好了
static {
// 最大值可以通过配置文件设置
int h = 127; //最大值127
//从jvm的配置中拿到自定义缓冲池最大值的参数
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
//integerCacheHighPropValue的值默认是null
if (integerCacheHighPropValue != null) {
try {
//拿到传入的最大值
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// 最大值不能超过Integer定义的最大值
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// 如果属性不能被解析成int型,就忽略它。
}
}
high = h; //赋值给high
//(high - low) + 1 = 256
cache = new Integer[(high - low) + 1];
int j = low;// 遍历 0 - 256
for(int k = 0; k < cache.length; k++)
// -127开始累加,并且都放到cache中,注意k是从0开始的
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}

看到这里也就明白了,-128-127是如何实现的,超过这个范围为什么是在堆中创建了,在这个源码中,还有一个地方没有解释,那就是参数java.lang.Integer.IntegerCache.high,这个参数可以加载到手动传入的值,从而扩大或者缩小缓冲池的最大值

如果我们设置这个值的大小为200:-Djava.lang.Integer.IntegerCache.high=200,那么我们就能看到下面的代码的结果是true

Integer e = 200;
Integer f = 200;
System.out.println(e == f);

看完上面的分析之后,我希望大家记住一点:当需要进行自动装箱时,如果数字在 -128 至 127 之间时,会直接使用缓存中的对象,而不是重新创建一个对象

注意:缓冲池只有Integer类型有

包装类可以为null,而基本类型不可以

别小看这一点区别,它使得包装类型可以应用于 POJO 中,而基本类型则不行。

那为什么 POJO 的属性必须要用包装类型呢?

对于基本数据类型,数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱(将包装类型转为基本类型,比如说把 Integer 对象转换成 int 值),就会抛出 NullPointerException 的异常。因为基础类型的值只能是数值。

包装类型可用于泛型,而基本类型不可以

我们先尝试定义一个List

List<int> list = new ArrayList<>();

报错:

Syntax error, insert "Dimensions" to complete ReferenceTypeList<Integer> list = new ArrayList<>();

原因是泛型只能使用Object 类及其子类,所以包装类型可用于泛型,而基本类型不可以

基本类型比包装类型更高效

基本数据类型在栈中直接存储具体的数值,而包装类型则存储在堆中,栈中存放的是引用

很显然,相对于基本数据类型而言,包装类型需要占用更多的内存空间,假如没有基本数据类型的话,对于数值这类经常能用到的数据来说,每次都要通过new来创建包装类型就显得非常笨重。

两个包装类型的值可以相同,但却可以不相等

两个包装类型的值可以相同,但却不相等

不相等是因为两个包装类型在使用“==”进行判断的时候,判断的是其指向的内存地址是否相等。包装类的值如果是在堆中创建出的话,因为内存地址不同,所以返回的是false。

而值相同是因为包装类型在做equals比较的时候,都先拆箱成了基础类型,然后再做比较,即比较的是内容,所以为true。

public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
Author: Tunan
Link: http://yerias.github.io/2020/03/17/java/11/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.