JAVA深化篇_03——包装类与自动拆装箱及缓存问题
基本数据类型的包装类
我们前面学习的八种基本数据类型并不是对象,为了将基本类型数据和对象之间实现互相转化,Java为每一个基本数据类型提供了相应的包装类。
包装类基本知识
Java是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据类型就不是对象。但是我们在实际应用中经常需要将基本数据转化成对象,以便于操作。比如:将基本数据类型存储到Object[ ]
数组或集合中的操作等等。
为了解决这个不足,Java在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
包装类位于java.lang包
,八种包装类和基本数据类型的对应关系:
基本数据类型 | 包装类 |
---|---|
byte | Byte |
boolean | Boolean |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
在这八个类名中,除了Integer
和Character类
以外,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写而已。
Number类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number类提供了抽象方法:intValue()
、longValue()
、floatValue()
、doubleValue()
,意味着所有的“数字型”包装类都可以互相转型。
下面我们通过一个简单的示例认识一下包装类。
【示例】初识包装类
public class WrapperClassTest {
public static void main(String[ ] args) {
Integer i = new Integer(10); //从java9开始被废弃
Integer j = Integer.valueOf(50); //官方推荐
}
}
包装类的用途
对于包装类来说,这些类的用途主要包含两种:
-
作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如
Object[ ]
、集合等的操作。 -
包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法(这些操作方法的作用是在基本数据类型、包装类对象、字符串之间提供相互之间的转化!)。
【示例】包装类的使用
public class Test {
/** 测试Integer的用法,其他包装类与Integer类似 */
void testInteger() {
// 基本类型转化成Integer对象
Integer int1 = new Integer(10); //已废弃,不推荐使用
Integer int2 = Integer.valueOf(20); // 官方推荐这种写法
// Integer对象转化成int
int a = int1.intValue();
// 字符串转化成Integer对象
Integer int3 = Integer.parseInt("334");
Integer int4 = new Integer("999");
// Integer对象转化成字符串
String str1 = int3.toString();
// 一些常见int类型相关的常量
System.out.println("int能表示的最大整数:" + Integer.MAX_VALUE);
}
public static void main(String[ ] args) {
Test test = new Test();
test.testInteger();
}
}
执行结果如图所示:
自动装箱和拆箱
自动装箱(autoboxing)和拆箱(unboxing):将基本数据类型和包装类自动转换。
自动装箱:
基本类型的数据处于需要对象的环境中时,会自动转为“对象”。
我们以Integer为例:
Integer i = 5
编译器会自动转成:Integer i = Integer.valueOf(5)
,这就是Java的自动装箱。
自动拆箱:
每当需要一个值时,对象会自动转成基本数据类型,没必要再去显式调用intValue()、doubleValue()等转型方法。
Integer i = Integer.valueOf(5);
int j = i;
编译器会自动转成:int j = i.intValue()
;
这样的过程就是自动拆箱。
自动装箱/拆箱的本质是:
自动装箱与拆箱的功能是编译器来帮忙,编译器在编译时依据您所编写的语法,决定是否进行装箱或拆箱动作。
【示例】自动装箱
Integer i = 100;//自动装箱
//相当于编译器自动为您作以下的语法编译:
Integer i = Integer.valueOf(100);//调用的是valueOf(100),而不是new Integer(100)
【示例】自动拆箱
Integer i = 100;
int j = i;//自动拆箱
//相当于编译器自动为您作以下的语法编译:
int j = i.intValue();
自动装箱与拆箱的功能是所谓的“编译器蜜糖(Compiler Sugar)”,虽然使用这个功能很方便,但在程序运行阶段您得了解Java的语义。如下所示的程序是可以通过编译的:
【示例】包装类空指针异常问题
public class Test1 {
public static void main(String[ ] args) {
Integer i = null;
int j = i;
}
}
执行结果如图所示:
运行结果之所以会出现空指针异常,是因为如上代码相当于:
public class Test1 {
public static void main(String[ ] args) {
/*示例8-5的代码在编译时期是合法的,但是在运行时期会有错误因为其相当于下面两行代码*/
Integer i = null;
int j = i.intValue();
}
}
包装类的缓存问题
整型、char类型所对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓存处理,其目的是提高效率。
缓存原理为:如果数据在-128~127这个区间,那么在类加载时就已经为该区间的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。每当自动装箱过程发生时(或者手动调用valueOf()时),就会先判断数据是否在该区间,如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new调用包装类的构造方法来创建对象。
下面我们以Integer类为例,看一看Java为我们提供的源码,加深对缓存技术的理解,如示例所示。
【示例】Integer类相关源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
-
为Integer类的一个静态内部类,仅供Integer类使用。
-
一般情况下
IntegerCache.low为-128
,IntegerCache.high为127
,
IntegerCache.cache
为内部类的一个静态属性,如示例所示。
【示例】IntegerCache类相关源码
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() {}
}
由上面的源码我们可以看到,静态代码块的目的就是初始化数组cache的,这个过程会在类加载时完成。
下面我们做一下代码测试,如示例所示。
【示例8-9】包装类的缓存测试
public class Test3 {
public static void main(String[ ] args) {
Integer in1 = -123;
Integer in2 = -123;
System.out.println(in1 == in2);//true 因为123在缓存范围内
System.out.println(in1.equals(in2));//true
Integer in3 = 1234;
Integer in4 = 1234;
System.out.println(in3 == in4);//false 因为1234不在缓存范围内
System.out.println(in3.equals(in4));//true
}
}
总结
1️⃣ 自动装箱调用的是valueOf()方法,而不是new Integer()方法。
2️⃣ 自动拆箱调用的xxxValue()方法。
3️⃣ 包装类在自动装箱时为了提高效率,对于-128~127之间的值会进行缓存处理。超过范围后,对象之间不能再使用==进行数值的比较,而是使用equals方法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本