Java的自动装箱与拆箱

140 阅读7分钟

为什么Java需要自动装箱和拆箱这一机制呢?比如,泛型(Java5引入),Java中的泛型只能使用引用类型,不能使用基本数据类型。还有集合框架(Java2引入),Java 的集合框架(如 List、Set、Map 等)只能存储对象(引用类型),不能存储基本数据类型。因此为了API的兼容性,代码的灵活性和便利性,就有了自动装箱和拆箱机制。它们使得基本数据类型与其对应的包装类之间的转换更加方便,同时也提高了代码的可读性和可维护性。

1.回顾基本数据类型和包装类基本数据类型(Primitive Data Types)

Java提供了一组基本数据类型,如整数(intbyteshortlong)、浮点数(floatdouble)、字符(char)、布尔(boolean)等。这些基本数据类型存储简单的数值或布尔值。

包装类(Wrapper Classes)

为了使基本数据类型具备面向对象的特性,Java提供了对应的包装类,如IntegerDoubleCharacterBoolean等。这些包装类用于将基本数据类型封装为对象,以便进行更多的操作。

基本数据类型和包装类如下图对应

基本类型包装类型
booleanBoolean
byteByte
charCharacter
floatFloat
intInteger
longLong
shortShort
doubleDouble

2.自动装箱和自动拆箱自动装箱

自动装箱是指将基本数据类型自动转换为相应的包装类对象。这意味着,你可以将一个int类型的值直接赋值给Integer类型的变量,而无需显式创建Integer对象。

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

对以上代码进行反编译后可以得到以下代码:

public static  void main(String[]args){
    int i=2;
    Integer wrappedInt=Integer.valueOf(i);
}

从上面反编译后的代码可以看出,int的自动装箱都是通过Integer.valueOf()方法来实现的。如果将所有的基本数据类型都尝试一遍,不难发现自动装箱的原理:

自动装箱原理:自动装箱是通过调用对应包装类型的valueOf()方法实现的。例如,将int类型装箱为Integer类型时,会调用Integer.valueOf(int)方法。valueOf()方法返回一个包装类型对象,其中封装了基本类型的值。这意味着装箱操作实际上是通过静态方法调用来完成的。

自动拆箱:自动拆箱是指将包装类对象自动转换为相应的基本数据类型。这意味着,你可以直接将一个Integer对象赋值给int类型的变量,而无需显式调用.intValue()方法。

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(); 
}

从上面反编译后的代码可以看出,Integer的自动拆箱都是通过Integer.intValue()方法来实现的。如果将所有的基本数据类型都尝试一遍,不难发现自动装箱的原理:
自动拆箱原理:自动拆箱是通过调用包装类型对象的xxxValue()方法实现的,其中xxx表示对应的基本类型。例如,将Integer类型拆箱为int类型时,会调用Integer.intValue()方法。拆箱操作会从包装类型对象中提取出基本类型的值,并将其赋给对应的基本类型变量。拆箱操作是通过实例方法调用来完成的。

3.自动装箱和拆箱的作用类型转换的便利性

在Java中,基本数据类型和包装类是两种不同的类型,它们之间不能直接进行赋值或传递参数。通过自动装箱和拆箱,可以在需要使用包装类对象的地方直接使用基本数据类型,而无需手动进行类型转换。

public static void main(String[] args) {
        // 自动装箱和拆箱
        Integer autoBoxing = 20; // 自动装箱
        int autoUnboxing = autoBoxing; // 自动拆箱
        System.out.println("autoBoxing: " + autoBoxing);
        System.out.println("autoUnboxing: " + autoUnboxing);
 }

泛型的支持

在泛型中,只能使用类对象,而不能使用基本数据类型。通过自动装箱和拆箱,可以方便地在泛型中使用基本数据类型,而无需手动进行类型转换。

public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        
        String firstPerson = names.get(0);
        String secondPerson = names.get(1);
        
        System.out.println("First person: " + firstPerson);
        System.out.println("Second person: " + secondPerson);
    }
  • 在上面的示例中,首先创建了一个List对象names,其元素类型为String。然后通过add方法向集合中添加字符串元素。接着使用get方法获取集合中的元素,并将其赋值给String类型的变量。最后,将获取到的两个字符串变量打印出来。
  • 可以看到,通过使用泛型,我们可以在编译时就确定集合中元素的类型,并在操作集合时获得类型安全的保证。这样,我们可以更加方便地在集合类中操作指定类型的元素。

集合类的支持

Java的集合类(如List、Set、Map等)只能存储对象,不能存储基本数据类型。通过自动装箱和拆箱,可以方便地将基本数据类型存储到集合中,而无需手动进行类型转换。

public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        
        // 添加元素时自动装箱
        numbers.add(10); // int -> Integer
        numbers.add(20); // int -> Integer
        
        // 访问元素时自动拆箱
        int firstNumber = numbers.get(0); // Integer -> int
        int secondNumber = numbers.get(1); // Integer -> int
    }
  • 在上面的示例中,首先创建了一个List对象numbers,其元素类型为Integer。然后通过add方法向集合中添加元素,这时编译器会自动将int类型的参数装箱为Integer对象。接着使用get方法获取集合中的元素,这时编译器会自动将Integer对象拆箱为int类型。
  • 可以看到,通过自动装箱和拆箱,我们可以直接将基本数据类型的值添加到集合中,并在需要时将集合中的元素转换回基本数据类型。这样,我们可以更加方便地在集合类中操作基本数据类型的值。

方法的重载

在方法的重载中,如果只有基本数据类型和对应包装类的方法签名不同,那么编译器会自动选择合适的方法。通过自动装箱和拆箱,可以方便地进行方法的重载,提高代码的可读性和灵活性。

public class BoxingExample {
    public static void printNumber(int num) {
        System.out.println("Printing int: " + num);
    }
    
    public static void printNumber(Integer num) {
        System.out.println("Printing Integer: " + num);
    }
    
    public static void main(String[] args) {
        int a = 10;
        Integer b = 20;
        
        printNumber(a); // 自动装箱,调用printNumber(Integer num)方法
        printNumber(b); // 调用printNumber(Integer num)方法
    }
}
  • 在上面的示例中,定义了两个重载的方法printNumber,一个接收int类型参数,一个接收Integer类型参数。在main方法中,首先定义了一个int类型的变量a和一个Integer类型的变量b。然后分别调用printNumber方法,并传入这两个变量。由于Java的自动装箱机制,编译器会自动将int类型的变量a装箱为Integer对象,然后选择调用printNumber(Integer num)方法。
  • 可以看到,通过自动装箱的机制,我们可以直接将基本数据类型和包装类进行混合使用,并且编译器会自动选择适合的方法。这样,我们可以更加方便地使用基本数据类型和包装类进行方法的重载。

自动装箱和拆箱是 Java 语言中一个重要的特性,它们让基本数据类型与包装类之间的转换更加便捷,提高了代码的可读性和可维护性。通过自动装箱和拆箱,Java 程序员可以更加专注于解决业务逻辑,而不必过多关注数据类型转换的细节。然而,在使用自动装箱和拆箱时,仍需谨慎考虑性能和空指针异常等方面的问题。