天天看点

详解JAVA中自动装箱与拆箱

作者:架构知识

一、简介

1、什么是自动装箱和拆箱

自动装箱和拆箱是Java语言中的一种特性。当我们需要将基本数据类型(如int、char等)转化为对应的包装类型(如Integer、Character等)时,自动装箱会自动将基本数据类型转化为对应的包装类型。相反地,当我们需要将包装类型转化为对应的基本数据类型时,自动拆箱会自动将包装类型转化为对应的基本数据类型。这种自动转化的特性使得Java编程更加方便和简便。

详解JAVA中自动装箱与拆箱

2、为什么需要自动装箱和拆箱

在Java语言中,基本数据类型和包装类型之间是不能直接相互赋值的,需要通过类型转换实现。而使用自动装箱和拆箱则可以省去手动转换的麻烦,直接将基本数据类型和对应的包装类型互相转换。这样可以使编程更加方便、简洁和易读,也提高了代码的可读性和易维护性。此外,在某些情况下,自动装箱和拆箱还可以避免一些潜在的错误。例如,在使用泛型类型时,自动装箱和拆箱可以避免类型转换错误,提高代码的安全性。

二、自动装箱

基本数据类型可以通过自动装箱或手动装箱转换为对应的包装类型,例如:

1、自动装箱

int num = 10;
Integer integer = num; // 自动装箱           

2、手动装箱

int num = 10;
Integer integer = Integer.valueOf(num); // 手动装箱           

注意:如果基本数据类型的值为null,则不能自动或手动转换为对应的包装类型。

int num = null; // 编译错误:基本类型错误
Integer integer = null; // 正常编译:包装类型可为空           

在使用自动装箱时,Java虚拟机会自动将基本数据类型封装为对应的包装类型对象,而在使用手动装箱时,需要使用包装类型的valueOf方法进行封装,这两种方法最终都会返回对应的包装类型对象。

三、自动拆箱

包装类型可以通过自动拆箱或手动拆箱转换为对应的基本数据类型,例如:

1、自动拆箱

Integer integer = 10;
int num = integer; // 自动拆箱           

2、手动拆箱

Integer integer = Integer.valueOf(10);
int num = integer.intValue(); // 手动拆箱           

注意:如果包装类型的值为null,则不能自动或手动转换为对应的基本数据类型。此时会抛出空指针异常(NullPointerException)。

Integer integer = null;
int num = integer; // 抛出空指针异常           

在使用自动拆箱时,Java虚拟机会自动获取包装类型对象的值,并且将其转换为对应的基本数据类型,而在使用手动拆箱时,需要使用包装类型的xxxValue方法获取其对应的基本数据类型值,这两种方法最终都会返回对应的基本数据类型值。

四、自动装箱和拆箱的实现方式

1、编译器插入方法调用

自动装箱和拆箱的实现方式是通过Java编译器进行代码转换的。编译器在遇到需要自动装箱或自动拆箱的情况时,会自动插入一些方法调用来完成转换。

具体来说,当需要自动装箱时,编译器会在代码中插入调用valueOf方法的代码,将基本类型值转换为对应的包装类型对象,例如:

int i = 10;
Integer j = i; // 自动装箱
// 编译器会转换为以下代码
Integer j = Integer.valueOf(i);           

当需要自动拆箱时,编译器会在代码中插入调用xxxValue方法的代码,将包装类型对象转换为基本类型值,例如:

Integer i = 10;
int j = i; // 自动拆箱
// 编译器会转换为以下代码
int j = i.intValue();           

这种转换过程对于开发人员是透明的,Java编程人员只需要编写自然的代码,Java编译器会在编译阶段自动完成转换操作。

2、性能开销的问题

自动装箱和自动拆箱虽然带来了方便,但是也带来了性能开销问题。由于转换过程需要调用方法,而方法调用是有一定的时间和空间开销的,通过自动装箱和自动拆箱带来的性能开销主要有以下两个方面:

  • 对象创建开销:自动装箱过程会创建一个新的对象来存储基本类型值,而每个对象都需要内存分配和垃圾回收。这样就会带来一定的时间和空间开销。
  • 方法调用开销:自动装箱和自动拆箱过程需要调用方法来完成转换,方法调用通常需要进行一些寻址和栈操作,这些操作也会带来时间和空间上的开销。

因此,在一些性能要求较高的场景下,应该尽量避免使用自动装箱和自动拆箱,而应该直接使用基本类型。同时,如果需要进行装箱操作,可以考虑使用静态工厂方法来创建对象,这样可以避免每次创建新对象带来的性能开销。

五、最佳实践

1、手动进行装箱和拆箱操作

手动进行装箱和拆箱操作可以避免自动装箱和拆箱所带来的性能开销,但需要在代码中显式地进行类型转换。以下是手动进行装箱和拆箱操作的示例代码:

手动装箱:

int i = 10;
Integer integer = Integer.valueOf(i);           

手动拆箱:

Integer integer = 10;
int i = integer.intValue();           

需要注意的是,在手动进行类型转换时,我们需要确保不会发生空指针异常或类型转换异常。如果不确定对象是否为null,我们需要进行空判断;如果不确定类型是否兼容,我们需要进行类型判断和转换。

为了提高程序的可读性和可维护性,我们可以使用命名静态工厂方法来进行装箱操作,如下所示:

public class MyInteger {
  private final int value;

  public MyInteger(int value) {
 		this.value = value;
  }

  public int intValue() {
  	return value;
  }

  public static MyInteger valueOf(int value) {
  	return new MyInteger(value);
  }
}           

使用MyInteger类进行手动装箱:

int i = 10;
MyInteger myInteger = MyInteger.valueOf(i);           

这样可以使代码更加简洁和易读。

2、考虑性能问题

手动进行装箱和拆箱操作的性能比自动进行装箱和拆箱操作要更优秀,因为自动装箱和拆箱操作会在运行时产生一些额外的开销。

手动装箱操作可以避免用于自动装箱操作的堆对象的创建,从而减少了垃圾回收的负载。而手动拆箱操作可以避免用于自动拆箱操作的对象的创建和销毁,从而避免了额外的性能开销。

因此,当我们需要进行性能敏感的操作时,手动进行装箱和拆箱操作是比较理想的选择。但需要注意的是,在性能与代码可读性之间做出权衡是非常重要的,因为手动进行装箱和拆箱操作可能会使代码更加复杂和难以理解。

六、总结

自动装箱和拆箱方便了程序员的编码,但同时也引入了额外的性能开销,因为每次进行自动装箱或拆箱操作都需要创建或销毁对象。因此,在编写需要高性能的代码时,使用基本数据类型而非封装类型可能更为适合。

同时,需要注意自动装箱和拆箱可能会产生空指针异常和类型转换异常等问题,在使用时需要谨慎。在需要使用封装类型的某些方法,如List的排序方法时,可以考虑手动进行装箱和拆箱操作,以提高程序性能。

继续阅读