基本数据类型和包装类型的区别、缓存池、自动拆箱装箱(面试题)
目录
1. 八种基本类型及对应包装类型
2. 基本类型和包装类型 区别
3. 自动拆箱装箱
3.1 自动装箱
3.2 自动拆箱
3.3 缓存池
4. 高频面试案例分析
1. 八种基本类型及对应包装类型
基本数据类型 | 类型描述 | 范围(指数形式) | 位数 | 包装类型 |
---|---|---|---|---|
byte | 整型(有符号) | -2 ^ 7 ~2 ^ 7 -1 | 1字节= 8bit | Byte |
short | 整型(有符号) | -2 ^ 15 ~2 ^ 15 -1 | 2字节=16bit | Short |
int | 整型(有符号) | -2 ^ 31 ~2 ^ 31 -1 | 4字节= 32bit | Integer |
long | 整型(有符号) | -2 ^ 63 ~2 ^ 63 -1 | 8字节= 64bit | Long |
float | 浮点型 | 1.401298e-45 ~ 3.402823e+38(绝对值) | 4字节=32bit | Float |
double | 浮点型 | 4.9000000e-324 ~ 1.797693e+308(绝对值) | 8字节=64bit | Double |
char | 字符型 | ‘\u0000’~’\uFFFF’ | 2字节=16bit | Character |
boolean | 布尔型 | 只有两个值:true 或 false | 1字节= 8bit | Boolean |
2. 基本类型和包装类型 区别
- 1.性能
基本类型:占用内存小,效率高,适合频繁使用的简单操作。
包装类型:因为是对象,涉及内存分配和垃圾回收,性能相对较低。
- 2. 存储位置
基本类型:如果是局部变量则保存在栈上面,如果是成员变量则在堆中。
包装类型:保存在堆上(成员变量,在不考虑JIT优化的栈上分配时,都是随着对象一起保存在堆上的)。
基本数据类型:
- 1、变量名指向具体的数值。
- 2、基本数据类型存储在栈上。
引用数据类型:
- 1、变量名指向的是存储对象的内存地址,在栈上。
- 2、内存地址指向的对象存储在堆上。
- 3. 初始化方式
基本类型:直接赋值
包装类型:需要采用new的方式创建
- 4. 比较方式
基本类型:比较用==,直接比较数值。
包装类型:比较时,==比较的是对象的内存地址,而equals()比较的是对象的值。
- 5. 默认值
基本类型:默认值是0,false 等。
包装类型:默认为null。
3. 自动拆箱装箱
3.1 自动装箱
装箱 :就是将基本数据类型用他们对应的包装类包装起来
Integer i = 10;
我们将上述代码反编译即得到:
Integer i = Integer.valueOf(10);
查看源码可知:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
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;
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() {}
}
在Integer的valueof(int i)中,我们传递进去的参数是基本数据类型int型,在函数内部如上所示利用了Integer内部的一个私有静态类IntegerCache,该类静态初始化了一个包含了Integer.IntegerCache.low 到 java.lang.Integer.IntegerCache.high的Integer数组。其中java.lang.Integer.IntegerCache.high的取值范围在[127~Integer.MAX_VALUE - (-low) -1]之间。
在该区间内所有的Integer.valueOf(int)函数返回的对象,是根据int值计算的偏移量,从数组Integer.IntegerCache.cache中获取,返回一个Integer对象。如果不在[IntegerCache.low~IntegerCache.high)之间,就会在JVM中分配内存new一个新的Integer对象。
因此,我们可以看出来装箱的时候其实就是自动调用了Integer的valueOf(int)方法。
3.2 自动拆箱
拆箱就是将包装类型转换为基本数据类型
如下:
Integer i = 10;//装箱
int n = i; //拆箱
反编译得到:
Integer i = Integer.valueOf(10);
int n = i.intValue();
查看源码:
private final int value;
public int intValue() {
return value;
}
可以看出,在拆箱的时候调用了Integer对象的intValue()方法。包装类在装箱时,将我们传入的int型参数value保存起来且私有不能被改变,在拆箱时利用intValue()方法返回int型的value值。
因此总结一下装箱和拆箱的实现过程就是:
装箱过程是通过调用包装类的valueOf方法实现的,而拆箱过程是通过调用包装类的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。
注意:我们要了解的是,频繁的装箱拆箱的话,会增加内存的消耗,影响性能
3.3 缓存池
java中有6个缓存池,基本数据类型的包装类除了 Float 和 Double 之外,其他六个包装器类(Byte、Short、Integer、Long、Character、Boolean)都有常量缓存池。
- Byte:-128~127,也就是所有的 byte 值
- Short:-128~127
- Long:-128~127
- Character:\u0000 - \u007F
- Boolean:true 和 false
下面详细说一下java中 Integer缓存池
Java的Integer缓存池(IntegerCache)是为了提升性能和节省内存。根据实践发现大部分的数据操作都集中在值比较小的范围,因此缓存这些对象可以减少内存分配和垃圾回收的负担,提升性能。
拿 Integer 来举例子
Integer 类内部中内置了 256 个 Integer 类型的缓存数据,当使用的数据范围在 -128~127 之间时,会直接返回常量池中数据的引用,而不是创建对象,超过这个范围时会创建新的对象。
new Integer(100)
每次都会新建一个对象;Integer.valueOf(100)
会使用缓存池中的对象,多次调用只会取同⼀一个对象的引用。
原理:
Java 在自动装箱时,对于值在-128到127之间的int类型,会直接返回一个已经缓存的Integer 对象,而不是创建新的对象。
缓存池的使用场景:
自动装箱(Auto-boxing):当基本类型int转换为包装类Integer时,若数值在缓存范围内,返回缓存对象。
值比较:由于相同范围内的整数使用同一个缓存对象,使用==可以正确比较它们的地址(引用相同),而不需要使用equals()。但是要注意对于超过缓存范围的Integer对象,==比较的是对象引用,而不是数值。要比较数值,应使用equals()方法。
4. 高频面试案例分析
下面这段代码的输出结果是什么呢?
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
Integer one=new Integer(100);
Integer two=new Integer(100);
Integer z = Integer.valueOf(18);
Integer k = Integer.valueOf(18);
System.out.println(z == k);//6
System.out.println(i1==i2); //1
System.out.println(i3==i4); //2
System.out.println(one==two); //3
System.out.println(i1==100); //4
System.out.println(i1==one); //5
}
提醒:
1. ”==“用于比较引用和比较基本数据类型时具有不同的功能,具体如下:
- 基本数据类型:比较的是他们的值是否相等,比如两个int类型的变量,比较的是变量的值是否一样。
- 引用数据类型:比较的是引用的地址是否相同,比如说新建了两个User对象,比较的是两个User的地址是否一样。
2. 装箱过程(基本类型转为包装类型)是通过调用包装类的valueOf方法实现的,而拆箱过程(包装类型转为基本类型)是通过调用包装类的 xxxValue方法实现的。(xxx代表对应的基本数据类型);
Integer包装类
当使用的数据范围在 -128~127 之间时,会直接返回常量池中数据的引用,而不是创建对象,超过这个范围时会创建新的对象。
new Integer(100)
每次都会新建一个对象;Integer.valueOf(8)
会使用缓存池中的对象,多次调用只会取同⼀一个对象的引用。
解答:
1,true,上面的代码中i1和i2的数值为100,是Integer包装类型且数据范围在 -128~127 之间,因此直接从IntegerCache(Integer缓存池)会直接返回常量池中数据的引用,而不是创建对象,超过这个范围时会创建新的对象。
2,false,这里很多同学容易出错,上面的代码中i3和i4的数值为200,根据IntegerCache私有静态类的源码可以发现,当数值不在[127~Integer.MAX_VALUE - (-low) -1]即[-128,127]之间时,便会创建新的Integer对象。因此这里i3和i4是两个不同对象,因此为false。
3,false,这里就很好理解了,因为new Integer(100);创建了两个不同的对象,==比较的是内存地址。
4,true,这里其实包含两个步骤:① Integer i1 = 100;进行装箱;② i1==100自动进行了拆箱,因此这里是两个基本数据类型进行比较,值必然相同。
i1
是一个Integer
对象,而100
是一个基本类型int
的值。在比较Integer
对象和基本类型int
时,Java会进行自动拆箱,将Integer
对象转换成int
值进行比较。由于i1
实际上是缓存中的对象,它的值是100
,所以i1
的值(100
)和基本类型int
的值(100
)是相等的,5,false,i1对象进行了装箱,其是直接从IntegerCache私有静态类的Integer数组中取已经存在的对象。而one是创建了一个新的对象,因此两者的内存地址必然不相同。
6,true,这里调用了valueOf方法 来装箱,第一次Integer z = Integer.valueOf(18);会创建 Integer对象值是18(在 -128~127 之间),并且放入Integer缓存池中,第二次Integer k = Integer.valueOf(18);会直接从缓存中返回 值为18的Integer对象的地址值,本质是同一个对象引用,所以z=k,true。
参考:
https://blog.csdn.net/qq_44085437/article/details/124028654