欢迎光临
我们一直在努力

【Java 开发日记】我们来说一说 Java 自动装箱与拆箱是什么?

目录

一、核心概念:什么是装箱与拆箱?

1. 手动装箱

2. 手动拆箱

二、什么是自动装箱与拆箱?

1. 自动装箱

2. 自动拆箱

三、实际应用场景举例

四、注意事项与陷阱(非常重要!)

1. 空指针异常

2. 性能消耗

3. 相等比较的陷阱

4. 三目运算符的陷阱

总结


一、核心概念:什么是装箱与拆箱?

要理解“自动”,首先要理解手动的“装箱”和“拆箱”。

Java 是一个面向对象的语言,但为了效率,它同时包含了两种不同的类型系统:

  • 基本数据类型:byte, short, int, long, float, double, char, boolean。它们直接存储“值”,存在于栈内存中,效率高。

  • 引用类型:所有 Object 的子类。它们存储的是对象的“引用”(地址),实际对象存在于堆内存中。

  • 在某些场景下(例如使用集合类 ArrayList, HashMap),我们必须使用引用类型,因为集合只能存储对象。这就产生了在基本类型和其对应的包装类对象之间转换的需求。

    包装类:Java 为每一个基本类型都提供了一个对应的“包装类”,将这些基本类型“包装”成对象。

    基本数据类型包装类
    byte Byte
    short Short
    int Integer
    long Long
    float Float
    double Double
    char Character
    boolean Boolean

    1. 手动装箱

    将一个基本数据类型的值,包装成对应的包装类对象。

    // 手动装箱
    int i = 10;
    Integer integerObj = Integer.valueOf(i); // 方式一:推荐,使用了缓存(后面会讲)
    // 或者
    Integer integerObj2 = new Integer(i); // 方式二:已过时 (Deprecated),不推荐

    2. 手动拆箱

    从一个包装类对象中,提取出它所包裹的基本数据类型的值。

    // 手动拆箱
    Integer integerObj = new Integer(10);
    int j = integerObj.intValue(); // 从 Integer 对象中取出 int 值


    二、什么是自动装箱与拆箱?

    从 Java 5 开始,为了简化开发,编译器提供了自动装箱和自动拆箱的功能。它本质上是一种“语法糖”,编译器在背后自动帮我们插入了转换代码,让我们可以用更简洁的方式编写。

    1. 自动装箱

    编译器自动将基本数据类型转换为对应的包装类对象。

    // 自动装箱
    int i = 10;
    Integer integerObj = i; // 编译器背后实际执行的是:Integer integerObj = Integer.valueOf(i);

    在这行代码中,一个 int 类型的变量 i 被直接赋给了一个 Integer 类型的引用。编译器在编译时,会悄悄地调用 Integer.valueOf(i) 来完成转换。

    2. 自动拆箱

    编译器自动将包装类对象转换为对应的基本数据类型。

    // 自动拆箱
    Integer integerObj = new Integer(10);
    int j = integerObj; // 编译器背后实际执行的是:int j = integerObj.intValue();

    在这里,一个 Integer 对象被直接赋给了一个 int 类型的变量。编译器在编译时,会悄悄地调用 integerObj.intValue() 来完成转换。


    三、实际应用场景举例

    自动装箱和拆箱让我们的代码变得非常简洁,尤其是在使用集合类时。

    // 在 Java 5 之前,使用 ArrayList 非常麻烦
    ArrayList list = new ArrayList();
    list.add(Integer.valueOf(1)); // 手动装箱
    int value = (Integer) list.get(0)).intValue(); // 取出来是 Object,要强转,再手动拆箱

    // 在 Java 5 之后,有了泛型和自动装箱/拆箱
    ArrayList<Integer> list = new ArrayList<>();
    list.add(1); // 自动装箱:int -> Integer
    int value = list.get(0); // 自动拆箱:Integer -> int。代码清晰多了!

    其他常见场景:

    // 1. 方法调用时传递参数
    public void processInteger(Integer i) { … }
    processInteger(5); // 自动装箱,将 int 5 转为 Integer

    // 2. 运算符运算时
    Integer a = 10;
    Integer b = 20;
    int c = a + b; // a 和 b 先自动拆箱为 int,相加后结果再自动装箱赋给 Integer(如果接收类型是Integer)
    // 等价于:int c = a.intValue() + b.intValue();

    // 3. 三目运算符中
    boolean flag = true;
    Integer i = flag ? 100 : 200; // 100和200都会被自动装箱为Integer


    四、注意事项与陷阱(非常重要!)

    自动装箱虽然方便,但也引入了一些容易忽略的陷阱。

    1. 空指针异常

    因为自动拆箱实际上是调用了 xxxValue() 方法,如果包装类对象是 null,调用方法就会抛出 NullPointerException。

    Integer nullInteger = null;
    int i = nullInteger; // 运行时抛出 NullPointerException!
    // 背后是:int i = nullInteger.intValue();

    2. 性能消耗

    虽然单次的装箱/拆箱开销很小,但在循环次数极多(例如上亿次)的场景下,频繁的创建和销毁对象会带来不必要的性能开销。

    long start = System.currentTimeMillis();
    Long sum = 0L; // 这里用的是包装类 Long,会触发自动装箱
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
    sum += i; // 每次循环:i自动装箱为Long,然后sum拆箱为long,相加后再装箱为Long
    }
    long end = System.currentTimeMillis();
    System.out.println("耗时:" + (end – start));
    // 将 Long sum = 0L 改为 long sum = 0L,性能会有巨大提升。

    3. 相等比较的陷阱

    第一题:以下的代码会输出什么?

    public class Main{
    public static void main(Sring[] args){

    Integer i1 = 100;
    Integer i2 = 100;
    Integer i3 = 200;
    Integer i4 = 200;

    System.out.println(i1 == i2);
    System.out.println(i3 == i4);
    }
    }

    运行结果:

    true
    false

    为什么会出现这样的结果?输出结果表明 i1 和 i2 指向的是同一个对象,而 i3 和 i4 指向的是不同的对象。此时只需一看源码便知究竟,下面这段代码是 Integer 的 valueOf 方法的具体实现:

    public static Integer valueOf(int i) {
    if(i >= -128 && i <= IntegerCache.high)
    return IntegerCache.cache[i + 128];
    else
    return new Integer(i);
    }

    其中 IntegerCache 类的实现为:

    private static class IntegerCache {
    static final int high;
    static final Integer cache[];

    static {
    final int low = -128;

    // high value may be configured by property
    int h = 127;
    if (integerCacheHighPropValue != null) {
    // Use Long.decode here to avoid invoking methods that
    // require Integer's autoboxing cache to be initialized
    int i = Long.decode(integerCacheHighPropValue).intValue();
    i = Math.max(i, 127);
    // Maximum array size is Integer.MAX_VALUE
    h = Math.min(i, Integer.MAX_VALUE – -low);
    }
    high = h;

    cache = new Integer[(high – low) + 1];
    int j = low;
    for(int k = 0; k < cache.length; k++)
    cache[k] = new Integer(j++);
    }

    private IntegerCache() {}
    }

    在通过 valueOf 方法创建 Integer 对象的时候,如果数值在 [-128,127] 之间,便返回指向 IntegerCache.cache 中已经存在的对象的引用;否则创建一个新的 Integer 对象。

    上面的代码中 i1 和 i2 的数值为100,因此会直接从 cache 中取已经存在的对象,所以 i1 和 i2 指向的是同一个对象,而 i3 和 i4 则是分别指向不同的对象。

    第二题:以下的代码会输出什么?

    public class Main {
    public static void main(String[] args) {

    Double i1 = 100.0;
    Double i2 = 100.0;
    Double i3 = 200.0;
    Double i4 = 200.0;

    System.out.println(i1==i2);
    System.out.println(i3==i4);
    }
    }

    运行结果:

    false
    false

    原因: 在某个范围内的整型数值的个数是有限的,而浮点数却不是。

    4. 三目运算符的陷阱

    这是一个非常隐蔽的陷阱。

    boolean flag = true;
    Integer i = flag ? 100 : Integer.valueOf(200);
    // 这没问题,因为 100 和 Integer.valueOf(200) 类型“一致”(都是Integer对象)

    Integer i = flag ? 100 : null;
    // 当 flag 为 false 时,会发生什么?
    // 编译器认为 100 是 int,null 是 Integer。为了类型一致,它会将 100 自动装箱为 Integer,将 null 作为 Integer。
    // 所以这里不会报错,i 会被赋值为 null。

    int j = flag ? 100 : Integer.valueOf(200);
    // 这也没问题,因为 Integer.valueOf(200) 会被自动拆箱为 int。

    int k = flag ? 100 : null;
    // 当 flag 为 false 时,会发生 NullPointerException!
    // 因为编译器需要得到一个 int 类型的结果,所以它会尝试对 `null` 进行自动拆箱,调用 null.intValue()。

    总结

    • 自动装箱:基本类型 -> 包装类,编译器调用 valueOf()。

    • 自动拆箱:包装类 -> 基本类型,编译器调用 xxxValue()。

    • 优点:简化代码,使基本类型和包装类之间的转换无缝进行。

    • 陷阱:

    • 空指针:包装类为 null 时拆箱会抛 NPE。

    • 性能:在密集循环中可能带来开销。

    • 比较:== 比较包装类是比较地址,应使用 equals 或先拆箱。

    • 三目运算符:要注意类型的统一,避免意外的自动拆箱。

    如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!

    赞(0)
    未经允许不得转载:171主机测评 » 【Java 开发日记】我们来说一说 Java 自动装箱与拆箱是什么?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址