【Java基础】之深入理解Java的拆箱和装箱

这篇文章将深入讲解Java的拆箱和装箱机制

☕️ 基本数据类型

基本类型,或者叫做内置类型,是Java中不同于类(Class)的特殊类型,也是我们编程中使用最频繁的类型。Java基本类型共有八种,基本类型可以分为三类:

  • 字符类型 char
  • 布尔类型 boolean
  • 数值类型 byte、short、int、long、float、double

其中,数值类型又可以分为:整数类型 byte、short、int、long 和浮点数类型 float、double

注意:Java中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变

基本数据类型的好处

我们都知道,在Java语言中,new 一个对象是存储在里的,我们通过栈中的引用来使用这些对象,所以,对象本身来说是比较消耗资源的。

对于经常用到的类型,如 int 等,如果我们每次使用这种变量的时候都需要 new 一个 Java 对象的话,就会比较笨重。所以,和 C++ 一样,Java 提供了基本数据类型,这种数据的变量不需要使用 new 创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效

整型的取值范围

Java 中的整型主要包含 byte、short、int 和 long 这四种,表示的数字范围也是从小到大的:

  • byte:byte 用1个字节来存储,范围为 -128(-2^7) 到 127(2^7-1),在变量初始化的时候,byte 类型的默认值为0
  • short:short 用2个字节存储,范围为 -32,768 (-2^15) 到 32,767 (2^15-1),在变量初始化的时候,short 类型的默认值为0,一般情况下,因为 Java 本身转型的原因,可以直接写为0
  • int:int 用4个字节存储,范围为 -2,147,483,648 (-2^31) 到 2,147,483,647 (2^31-1),在变量初始化的时候,int类型的默认值为0
  • long:long用8个字节存储,范围为-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1),在变量初始化的时候,long 类型的默认值为 0L 或 0l(小写字母l),也可直接写为0

从上面可以看出每个整型类型都有一定的表示范围,所以,如果在程序中的计算超出了表示范围则会发生溢出,比如:

int i = Integer.MAX_VALUE;
int j = Integer.MAX_VALUE;
int k = i + j;
System.out.println("i(" + i + ") + j(" + j + ") = k(" + k + ")");

>>>>>
i(2147483647) + j(2147483647) = k(-2)

溢出的时候并不会抛异常,也没有任何提示。所以,在程序中,使用同类型的数据进行运算的时候,一定要注意数据溢出的问题


☕️ 包装类型

Java 语言是一个面向对象的语言,但是 Java 中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这八个和基本数据类型对应的类统称为包装类(Wrapper Class),包装类均位于 java.lang 包,包装类和基本数据类型的对应关系如下表所示

基本数据类型对应的包装类基本数据类型对应的包装类
byteBytebooleanBoolean
shortShortcharCharacter
intIntegerlongLong
floatFloatdoubleDouble

在这八个类名中,除了 Intege r和 Character 类以后,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写即可

为什么需要包装类

既然 Java 中为了提高效率,提供了八种基本数据类型,为什么还要提供包装类呢?因为 Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。

所以,为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。


☕️ 拆箱与装箱

那么,有了基本数据类型和包装类,肯定有些时候要在他们之间进行转换。比如把一个基本数据类型的int转换成一个包装类型的Integer对象。

  • 把基本数据类型转换成包装类的过程就是打包装,专业术语叫装箱(boxing)
  • 把包装类转换成基本数据类型的过程就是拆包装,专业术语叫拆箱(unboxing)

☕️ 自动拆箱与自动装箱

在Java SE 5之前,要进行装箱,需要通过以下代码:

Integer i = new Integer(10);

在 Java SE 5 之后,为了减少开发人员的工作,Java 提供了自动拆箱与自动装箱功能

  • 自动装箱: 就是将基本数据类型自动转换成对应的包装类。
  • 自动拆箱:就是将包装类自动转换成对应的基本数据类型。

比如:

Integer i = 10;  // 自动装箱
int j = i;		 // 自动拆箱

Integer i = 10 可以替代 Integer i = new Integer(10); 这就是因为 Java 帮我们提供了自动装箱的功能,不需要开发者手动去 new 一个 Integer 对象了。

☕️ 自动拆箱与自动装箱的原理

既然 Java 提供了自动拆装箱的能力,那么我们就来看一下,Java是如何实现自动拆装箱功能的。

我们有以下自动拆装箱的代码:

public static void main(String[] args) {
	Integer integer = 1;  // 自动装箱
	int i = integer;	  // 自动拆箱
}

对以上代码进行反编译:

public static void main(String[] args) {
	Integer integer = Integer.valueOf(1);
	int i = integer.intValue();
}

从上面反编译后的代码可以看出,int 的自动装箱都是通过 Integer.valueOf() 方法来实现的,Integer 的自动拆箱都是通过 integer.intValue 来实现的。类似的,如果将八种类型都反编译一遍,可以发现一个规律:

自动装箱都是通过包装类的 valueOf() 方法来实现的,自动拆箱都是通过包装类对象的 xxxValue() 来实现的,这里的 xxx 表示对应的基本类型。

☕️ 自动拆装箱的应用场景

包装类和基本类形之间的运算

包括包装类与基本类型之间的赋值、比较、算数运算等,以下面的代码为例:

// 赋值
Integer i = 10;

// 比较大小
i > 1 ? "大于" : "不大于";

// 真假判断
Boolean b = true;
b ? "真" : "假";

// 算数运算
int j = i + 5;

反编译后的代码为:

Integer i = Integer.valueOf(10);
i.intValue() > 1 ? "大于" : "不大于";
b.booleanValue() ? "真" : "假";
int j = i.intValue() + 5;

可以看到,包装类与基本数据类型进行各种运算时,是先将包装类进行拆箱成基本数据类型,然后进行运算的。

注意:在进行三目运算符的时候,由于三目运算符的语法规范,当第二,第三位操作数分别为基本类型和包装类时,其中的包装类就会拆箱为基本类型进行操作。比如:flag ? i : j; 如果第二段的 i 为包装类型的对象,而第三段 j 为基本类型时,就会对包装类 i 进行自动拆箱,如果这个时候 i 的值为 null,那么久会发生 NullPointerException 异常

基本数据类型放入集合类

我们知道,Java 中的集合类只能接收对象类型,但放入基本类型却是合法的,比如:

List<Integer> li = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    li.add(i);
}

我们将上面代码进行反编译来看一下:

List<Integer> li = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    li.add(Integer.valueOf(i));	 // 进行了自动装箱
}

从中可以看出,当我们把基本数据类型放入集合类的时候,会进行自动装箱。

作为函数参数和返回值

这个就比较好理解了,比如:

// 如果传递的参数为基本类型则参数自动装箱为包装类,返回时则会自动拆箱为基本类型
public int getNum(Integer num) {
	return num;
}

// 如果传递的参数为包装类时则参数会自动拆箱为基本类型,返回时则会装箱为包装类
public Integer getNum(int num) {
	return num;
}

☕️ 自动拆装箱与缓存

先看一下下面的代码:

Integer integer1 = 5;
Integer integer2 = 5;
Integer integer3 = 300;
Integer integer4 = 300;
System.out.println(integer1 == integer2);
System.out.println(integer3 == integer4);

>>>>>
true
false

我们都知道,在 Java 中,== 比较的是对象的引用,所以,在这个例子中,不同的对象有不同的引用,进行比较的时候,理应该都返回 false 才对的呀,可第一个比较却返回了 true,这是为什么呢?

原因是 Integer 中的缓存机制。在 Java 5 中,Integer 的操作上引入了一个新功能来节省内存和提高性能:整型对象通过使用相同的对象引用实现了缓存和重用。但又限制条件:

  • 适用于整数值区间-128 至 +127
  • 只适用于自动装箱,使用构造函数创建包装类对象不适用

我们只需要知道,当需要进行自动装箱时,如果数字在 -128 至 127 之间时,会直接使用缓存中的对象,而不是重新创建一个对象


☕️ 自动拆装箱的弊端

  • 包装对象的数值比较,不能简单的使用 ==,虽然 -128 到 127 之间的数字可以,但是这个范围之外还是需要使用 equals 比较
  • 自动拆箱时,如果包装类对象为 null,那么就会抛出 NullPointerException 异常
  • 如果一个for循环中有大量拆装箱操作,会浪费很多资源
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值