✅Java中有了基本类型为什么还需要包装类?
- ✅Java中有了基本类型为什么还需要包装类
- ✅Java的8中基本数据类型
- ✅知识拓展
- ✅基本类型和包装类型的区别
- ✅如何理解自动拆装箱?
- ✅拆箱与装箱
- ✅自动拆装箱
- ✅自动拆装箱原理
- ✅哪些地方会自动拆装箱?
- 🍁场景一:将基本数据类型放入集合类
- 🍁场景二:包装类型和基本类型的大小比较
- 🍁场景三:包装类型的运算
- 🍁场景四:三目运算符的使用
- 🍁场景五:函数参数与返回值
- ✅自动拆装箱与缓存
✅Java中有了基本类型为什么还需要包装类
✅Java的8中基本数据类型
Java中的8中基本数据类型,这些基本类型又都有对应的包装类。看一下表格:
数据类型 | 分类 | 描述 | 取值范围 | 示例 | 包装类 | 对应长度 |
---|---|---|---|---|---|---|
byte | 整数类型 | 8位有符号二进制整数 | -128 到 127 | byte a = -50; | Byte | 1字节 |
short | 整数类型 | 16位有符号二进制整数 | -32768 到 32767 | short b = 10000; | Short | 2字节 |
int | 整数类型 | 32位有符号二进制整数 | -2^31 到 2^31-1 | int c = -20000; | Integer | 4字节 |
long | 整数类型 | 64位有符号二进制整数 | -2^63 到 2^63-1 | long d = 1000000000L; | Long | 8字节 |
float | 小数类型 | 单精度浮点数(32位IEEE 754) | 由IEEE 754标准定义,约±3.4E±38F | float e = 3.14f; | Float | 4字节(IEEE 754单精度浮点数) |
double | 小数类型 | 双精度浮点数(64位IEEE 754) | 由IEEE 754标准定义,约±1.7E±308D | double f = 2.71828D; | Double | 8字节(IEEE 754双精度浮点数) |
char | 字符类型 | 16位Unicode字符(UTF-16编码) | U+0000 到 U+FFFF(包括Unicode字符和特殊字符) | char g = ‘A’; char h = ‘\u0041’; | Character | 2字节(UTF-16编码) |
boolean | 布尔类型 | 布尔类型,只能取两个值:true和false | true 或 false | boolean i = true; boolean j = false; | Boolean | -(布尔类型的长度不固定) |
因为Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将int、double等类型放进去的。因为集合的容器要求元素是Obiect类型。
为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,主富了基本类型的操作。
✅知识拓展
✅基本类型和包装类型的区别
基本类型和包装类型在Java中有着明显的区别。以下是一些主要的差异:
基本类型:
- 它们是简单的数据类型,用于存储基础值,如整数、浮点数、字符等。
- 它们的值直接存储,不包含任何额外的信息。
- 它们是原始数据类型,没有方法,只有一些预定义的操作。
包装类型:
- 包装类型是基本类型的类表示。
- 包装类型提供了对基本类型的封装,可以防止数据的直接访问和修改。
- 包装类型可以包含方法,扩展了基本类型的功能。
总而言之:
1.默认值不同,基本类型的默认值为0,false或 u0000等,包装类默认为null
2.初始化方式不同,一个需要new,一个不需要
3.存储方式不同,基本类型保存在栈上,包装类对象保存在堆上(成员变量的话,在不考虑川T优化的栈上分配时,都是随着对象一起保存在堆上的)
public class DataTypeMain {
public static void main(String[] args) {
// 基本类型
int basicInt = 10;
System.out.println("基本类型的值: " + basicInt);
// 包装类型
Integer wrappedInt = new Integer(10);
System.out.println("包装类型的值: " + wrappedInt);
// 包装类型的方法
wrappedInt = wrappedInt + 5; // 使用包装类型的加法方法
System.out.println("包装类型的新值: " + wrappedInt);
// 基本类型不能直接进行加法操作,需要手动转换包装类型为基本类型,再执行加法操作
basicInt = basicInt + (int) wrappedInt;
System.out.println("基本类型的新值: " + basicInt);
// 装箱和拆箱操作
Integer boxedInt = basicInt; // 装箱操作,将基本类型转换为包装类型
System.out.println("装箱后的值: " + boxedInt);
int unboxedInt = (int) boxedInt; // 拆箱操作,将包装类型转换为基本类型
System.out.println("拆箱后的值: " + unboxedInt);
}
}
✅如何理解自动拆装箱?
✅拆箱与装箱
包装类是对基本类型的包装,所以,把基本数据类型转换成包装类的过程就是装箱,反之,把包装类转换成基本数据类型的过程就是拆箱。
看一段代码:
// 装箱
Integer boxedInt = 10; // 将基本类型int转换为包装类Integer
// 拆箱
int unboxedInt = boxedInt; // 将包装类Integer转换为基本类型int
在这个例子中,我们将一个基本类型int的值10赋值给一个包装类Integer的变量boxedInt。由于Java的类型转换机制,我们可以将基本类型的值直接赋值给包装类的变量,这就是装箱过程。
接下来,我们将一个包装类Integer的变量boxedInt赋值给一个基本类型int的变量unboxedInt。由于Java的类型转换机制,我们可以将包装类的值直接赋值给基本类型的变量,这就是拆箱过程。
需要注意的是,在进行装箱和拆箱操作时,Java会自动进行类型转换。但是,如果基本类型的值超出了包装类所能表示的范围,那么会发生溢出或下溢的情况。因此,在进行装箱和拆箱操作时,需要注意数据类型的范围和溢出问题。
✅自动拆装箱
在Java SE5中,为了减少开发人员的工作作,Java提供了自动拆箱与自动装箱功能。
自动装箱 : 就是将基本数据类型自动转换成对应的包装类。
自动拆箱: 就是将包装类自动转换成对应的基本数据类型。
看一段代码:
import java.util.Scanner;
public class AdvancedCalculator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入第一个数字:");
Integer num1 = scanner.nextInt(); // 自动装箱
System.out.print("请输入第二个数字:");
Integer num2 = scanner.nextInt(); // 自动装箱
System.out.println("请选择运算:");
System.out.println("1. 加法");
System.out.println("2. 减法");
System.out.println("3. 乘法");
System.out.println("4. 除法");
int operation = scanner.nextInt(); // 自动装箱
switch (operation) {
case 1: // 加法
Integer result = add(num1, num2); // 自动拆箱,将Integer对象转换为int值进行加法运算
System.out.println("加法结果为:" + result); // 自动装箱,将int值转换为Integer对象输出
break;
case 2: // 减法
result = subtract(num1, num2); // 自动拆箱,将Integer对象转换为int值进行减法运算
System.out.println("减法结果为:" + result); // 自动装箱,将int值转换为Integer对象输出
break;
case 3: // 乘法
result = multiply(num1, num2); // 自动拆箱,将Integer对象转换为int值进行乘法运算
System.out.println("乘法结果为:" + result); // 自动装箱,将int值转换为Integer对象输出
break;
case 4: // 除法
result = divide(num1, num2); // 自动拆箱,将Integer对象转换为int值进行除法运算
System.out.println("除法结果为:" + result); // 自动装箱,将int值转换为Integer对象输出
break;
default:
System.out.println("无效的运算选择!");
break;
}
}
public static Integer add(Integer a, Integer b) {
return a + b; // 自动拆箱,将输入的两个Integer对象转换为int值进行加法运算
}
public static Integer subtract(Integer a, Integer b) {
return a - b; // 自动拆箱,将输入的两个Integer对象转换为int值进行减法运算
}
public static Integer multiply(Integer a, Integer b) {
return a * b; // 自动拆箱,将输入的两个Integer对象转换为int值进行乘法运算
}
public static Integer divide(Integer a, Integer b) {
if (b == 0) { // 检查除数是否为零,避免出现异常情况
System.out.println("除数不能为零!");
return null; // 返回null表示除法运算失败
} else {
return a / b; // 自动拆箱,将输入的两个Integer对象转换为int值进行除法运算,并返回结果
}
}
}
在上面的示例中,使用Java的自动装箱和拆箱机制,将用户输入的int值转换为Integer对象,并在计算结果时将Integer对象转换为int值输出。在add、subtract、multiply和divide方法中,我们将输入的两个Integer对象转换为int值进行相应的运算,并返回结果。在divide方法中,我们还需要检查除数是否为零,以避免出现异常情况。这个过程就是自动拆箱的过程。
✅自动拆装箱原理
自动装箱都是通过包装类的 value0f() 方法来实现的自动拆箱都是通过包装类对象的 xxxValue()来实现的。
如: int的自动装箱都是通过 Integer.value0f() 方法来实现的,Integer的自动拆箱都是通过 nteger.intValue()来实现的。
✅哪些地方会自动拆装箱?
我们了解过原理之后,在来看一下,什么情况下,Java会帮我们进行自动拆装箱。前面提到的变量的初始化和赋值的场景就不个绍了,那是最简单的也最容易理解的。
我们主要来看一下,那些可能被忽略的场景。
🍁场景一:将基本数据类型放入集合类
我们知道,Java中的集合类只能接收对象类型,那么以下代码为什么会不报错呢?
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i ++){
li.add(i);
}
将上面代码进行反编译,可以得到以下代码:
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50, i ++){
li.add(Integer.value0f(i));
}
以上,我们可以得出结论,当我们把基本数据类型放入集合类中的时候,会进行自动装箱。
🍁场景二:包装类型和基本类型的大小比较
有没有人想过,当我们对Integer对象与基本类型进行大小比较的时候,实际上比较的是什么内容呢? 看以下代码:
Integer a=1;
System.out.println(a==1?"等于":"不等于");
Boolean bool=false;
System.out.printIn(bool?"真":"假");
对以上代码进行反编译,得到以下代码:
Integer a=1;
System.out.println(a.intValue()==1?"等于":"不等于");
Boolean bool=false;
Svstem.out.println(bool.booleanValue?"真":"假");
可以看到,包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,然后进行比较的。
🍁场景三:包装类型的运算
有没有人想过,当我们对Integer对象进行四则运算的时候,是如何进行的呢? 看以下代码:
Integer i = 10;
Integer j = 20;
System.out.println(i+j);
反编译以后:
Integer i= Integer.value0f(10);
Integer j = Integer.valueOf(20);
System.out.println(i.intValue() + j.intValue());
我们发现,两个包装类型之间的运算,会被自动拆箱成基本类型进行。
🍁场景四:三目运算符的使用
这是很多人不知道的一个场景,作者也是一次线上的血淋淋的Bug发生后才了解到的一种案例。看一个简单的三目运算符的代码:
boolean flag = true; //设置成true,保证条件表达式的表达式二一定可以执行
boolean simpleBoolean = false; //定义一个基本数据类型的boolean变量
Boolean nullBoolean = null;//定义一个包装类对象类型的Boolean变量,值为null
boolean x = flag ? nullBoolean : simpleBoolean; //使用三目运算符并给x变量赋值
很多人不知道,boolean x = flag ? nullBoolean : simpleBoolean ;这一行,会发生自动拆箱。反编译后代码如下:
boolean flag = true;
boolean simpleBoolean = false;
Boolean nu11Boolean = null;
boolean x = flag ? nullBoolean.booleanValue() : simpleBoolean;
这其实是三目运算符的语法规范。当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。
可以看到,反编译后的代码的最后一行,编译器帮我们做了一次自动拆箱,而就是因为这次自动拆箱,导致代码出现对于一个null对象 (nullBoolean.booleanValue0) 的调用,导致了NPE。
🍁场景五:函数参数与返回值
这个比较容易理解,这季节上代码哈。
//自动拆箱
public int getNum1(Integer num) {
return num;
}
//自动装箱
public Integer getNum2(int num) {
return num;
}
✅自动拆装箱与缓存
Java SE的自动拆装箱还提供了一个和缓存有关的功能,我们先来看以下代码,猜测一下输出结果:
public static void main(String... strings) {
Integer integer1 = 3;
Integer integer2 = 3;
if (integer1 == integer2)//这里省略了大括号是可以的
System.out.println("integer1 == integer2") ;
else
System.out.println("integer1 != integer2");
Integer integer3 = 300;
Integer integer4 = 300;
if (integer3 == integer4)
System.out.println("integer3 == integer4");
else
System.out.println("integer3 != integer4");
}
我们普遍认为上面的两人判新的结果都是talse。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个i判断都是false的。在Java中,==比较的是对象用,而equals比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false。奇怪的是,这里两个类似的i条件判断返回不同的布尔值。
上面这段代码真正的输出结果:
原因就和Integer中的缓存机制有关。在Java 5中,在nteger的操作上引入了一个新功能来节省内存和提高性能。
整型对象通过使用相同的对象引用实现了缓存和重用。
适用于整数值区间 -128 至 +127
只适用于自动装箱,使用构造函数创建对象不适用。
我们只需要知道,当需要进行自动装箱时,如果数字在-128至127之间时,会直接使用缓存中的对象,而不是重新创建个对象。
其中的javadoc详细的说明了缓存支持-128到127之间的自动装箱过程。最大值127可以通过 -XX:AutoBoxCacheMax=size 修改。
实际上这个功能在Java 5中引入的时侯;范围是固定的-128至 +127。后来在Java 6中,可以通过 java.lang.Integer.IntegerCache.high 设置最大值。
这使我们可以根据应用程序的实际情况灵活地调整来提高性能。到底是什么原因选择这个-128到127范围呢?因为这个范围的数字是最被广泛使用的。在程序中,第一次使用Integer的时候也需要一定的额外时间来初始化这个缓
存。
在Boxing Conversion部分的Java语言规范(LS)规定如下:
如果一个变量p的值是:
-128至127之间的整数(S3.19.1)
true 和 false的布尔值 (s3.10.3)
'\u8808' 至 '\u897F'之间的字符(§3.19.4)
范围内的时,将p包装成a和b两个对象时,可以直接使用a==b判断a和b的值是否相等。