Java基础理论:深入理解Java自动装箱和自动拆箱(仅这篇就能理解透测)

imwucx 2023-07-15 12:24:22
  1. 什么是Java自动装箱?
  2. 什么是Java自动拆箱?

目录

一、基本概念

1.1 自动装箱

1.2 自动拆箱

二、深入字节码理解如何实现自动装箱和自动拆箱

2.1 示例代码

2.2 分析自动装箱和自动拆箱原理

三、面试中的问题解析

3.1 以下代码输出结果是什么?

3.2 以下代码输出结果是什么?

3.3 以下代码输出结果是什么?


一、基本概念

Java基本数据类型和其对应的包装类

bit(1字节)Byte
char(2字节)Character
short(2字节)Short
int(4字节)Integer
float(4字节)Float
double(8字节)Double
long(8字节)Long
boolean()Boolean

1.1 自动装箱

        自动将基本数据类型转换为包装器类型

// 自动装箱 相当于 Integer a = Integer.valueOf(66);
Integer a = 66;

1.2 自动拆箱

        自动将包装器类型转换为基本数据类型

Integer i = new Integer(100);
// 自动拆箱 相当于 int n = i.intValue();
int n = i;

 

注:在jdk1.5之前是无自动装箱的,如果要创建一个包装类型整型只能这样 Integer i = new Integer(9)来创建。

二、深入字节码理解如何实现自动装箱和自动拆箱

2.1 示例代码

写一段简单的代码,然后看看二进制码是什么样子

/**
 * 自动装箱和自去拆箱 <br/>
 *
 * @author yubaba
 * @date 2023/7/15 08:53:47
 */
public class AutoPack {

    public static void main(String[] args) {
        Integer i = 66;
        int n = i;
    }
}

2.2 分析自动装箱和自动拆箱原理

2.2.1 编译反汇编

先编译,然后用javap -c,反编译查看字节码是什么样子

  • javap: javap是Java开发工具包(JDK)中的一个命令行工具,它可以反编译Java类文件并输出类的字节码
  • -c: 选项可以输出类的字节码指令

使用命令 javac AutoPack.java,得到AutoPack.class,如下图:

而想看懂AutoPack.class文件,可以用反编译工具(如jd-gui),也可以用javap命令查看。

javap命令如下:

 主要关注两个 -v 和 -c

  • -v: 是对代码进行反汇编同时输出更多附加信息(有常量池信息、内存分配等信息)
  • -c: 是对代码进行反汇编

用命令javap -v AutoPack.class,输下如下:

Classfile /Users/changx/Documents/ws/test/AutoPack.class
  Last modified 2023-7-15; size 375 bytes
  MD5 checksum 2d1985a02dfdf427efd0659a02bfb263
  Compiled from "AutoPack.java"
public class AutoPack
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Methodref          #15.#16        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Methodref          #15.#17        // java/lang/Integer.intValue:()I
   #4 = Class              #18            // AutoPack
   #5 = Class              #19            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               AutoPack.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Class              #20            // java/lang/Integer
  #16 = NameAndType        #21:#22        // valueOf:(I)Ljava/lang/Integer;
  #17 = NameAndType        #23:#24        // intValue:()I
  #18 = Utf8               AutoPack
  #19 = Utf8               java/lang/Object
  #20 = Utf8               java/lang/Integer
  #21 = Utf8               valueOf
  #22 = Utf8               (I)Ljava/lang/Integer;
  #23 = Utf8               intValue
  #24 = Utf8               ()I
{
  public AutoPack();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=1
         0: bipush        66
         2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: astore_1
         6: aload_1
         7: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
        10: istore_2
        11: return
      LineNumberTable:
        line 10: 0
        line 11: 6
        line 12: 11
}
SourceFile: "AutoPack.java"

2.2.2 字节码指令说明

主要看main中的命令,指令说明:

  • bipush: 将常量66压入栈中。JVM根据取值范围不同,用不同的指令入栈,如下:
    • 取值 -1~5 采用 iconst 指令
    • 取值 -128~127 采用 bipush 指令
    • 取值 -32768~32767 采用 sipush 指令
    • 取值 -2147483648~2147483647 采用 ldc 指令
  • invokestatic: 是调用静态方法。调用的静态方法是 #2,#2指Constant pool中的#2
    • #2 Methodref是方法引用 是#15.#16,即Integer.valueOf()方法
      • #15是Class是类 Integer
      • #16是valueOf()
    • 即:invokestatic是调Integer.valueOf方法。
  • astore_1: 将栈顶元素赋值给本地变量表的1位置上。
  • aload_1: 把存放在局部变量表中索引1位置的对象引用压入操作栈。
  • invokevirtual: 调用非私有实例方法。调用方法是#3,#3是Constant pool中的#2
    • #2 Methodref是方法引用 是#15.#17,即Integer.intValue()方法
      • #15是Class是类 Integer
      • #17是intValue()
    • 即:invokevirtual是调Integer.intValue()方法
  • astore_2: 将栈顶元素赋值给本地变量表的2位置上。

2.2.3 结论

了解了反汇编指命的意思,大概应该就知道源码AutoPack.java在JVM是中如何执行的了。意思如下:

  1. Integer i = 66 是调Integer.valueOf(66)包装成包装类赋给i。即自动包装
  2. int n = i 是调Integer.intValue()方法即,i.intValue()方法拆箱成基本数据类型,赋值给n。即自动拆箱

三、面试中的问题解析

3.1 以下代码输出结果是什么?

3.1.1 问题与答案

public class Main {
    public static void main(String[] args) {
        Integer i1 = 66;
        Integer i2 = 66;
        Integer i3 = 166;
        Integer i4 = 166;
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

也许,我们都会说都是false,或者也有人说都是true。而事实结果是

true
false

为什么?不都是调Integer.valueOf()返回Integer对象么?。。

3.1.2 分析

要准确回答正确这个问题,我们看看Integer.valueOf()方法是如何实现的,源码如下:

// jdk 1.8

public final class Integer extends Number implements Comparable<Integer> {

    ...

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];
        static {
            int h = 127;
            String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            } 
            high = h;
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
        private IntegerCache() {}
    }
    ...
    public static Integer valueOf(int i) {
        if (i >= Integer.IntegerCache.low && i <= Integer.IntegerCache.high)
        return Integer.IntegerCache.cache[i + (-Integer.IntegerCache.low)];
        return new Integer(i);
    }

    ...

}

说明:

  • IntegerCache内部静态类
    • 最小值low为-128
    • 最大值high默认的最小值为127
  • valueOf(int i)方法的参数i 在 [IntegerCache.low, IntegerCache.high] 区间时,从缓存中取对象。

3.1.3 结论

  • Integer i1 = 66 是从缓存 IntegerCache.cache中取
  • Integer i2 = 66 也是从缓存 IntegerCache.cache中取,所以 i1 和 i2 指同一个对象,i1 == i2 为 true
  • Integer i3 = 166,(IntegerCache.high默认为127)166 > 127,所有 new Integer(166)给i3
  • Integer i4 = 166,(IntegerCache.high默认为127)166 > 127,也是 new Integer(166)给i4,所以 i3 和 i4 指不同的对象,所以 i3 == i4 为 false

3.2 以下代码输出结果是什么?

public class Main {
    public static void main(String[] args) {    
        Double d1 = 66.0;
        Double d2 = 66.0;
        Double d3 = 166.0;
        Double d4 = 166.0; 
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

注:跟3.1思路 一样。其它数据类型也是如此。

3.3 以下代码输出结果是什么?

public class Main {
    public static void main(String[] args) { 
        Integer a = 2;
        Integer b = 4;
        Integer c = 6;
        Integer d = 6;
        Integer e = 166;
        Integer f = 166;
        Long g = 6L;
        Long h = 4L;
        System.out.println(c==d);         //1
        System.out.println(e==f);         //2
        System.out.println(c==(a+b));     //3
        System.out.println(c.equals(a+b));//4
        System.out.println(g==(a+b));     //5
        System.out.println(g.equals(a+b));//6
        System.out.println(g.equals(a+h));//7
    }
}
  • //1 和 //2 应该没有什么疑问。true,false
  • //3 有算术运算,会自动拆箱(调 intValue()方法),因此是比较数据值。true
  • //4 有算术运算,有对象比较。a + b 调intValue自动拆箱成基本数据类型相加,得运算后的数值再调用Integer.valueOf方法,再进行equals比较。true
  • //5 跟 //3相同,是数值比较。true
  • //6 跟 //4相似 (假设m为 a + b后的包装对象),最后是 Long的g.equals(m),而g.equals方法是先判断 m instanceof Long是否为真,所以是 false
  • //7 a + h是long和int相加,为防止溢出返回的是long值 < 127(Long的缓存区间是-128-127),再执行equals比较,所以是 true

可以自己运行一下看输出结果。

...全文
374 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

291,272

社区成员

发帖
与我相关
我的任务
社区描述
一个有温度的 Java 爱好者社区,区长是 CSDN 头牌沉默王二,30万+读者,博客访问量 1000万+,全网屈指可数的博主~
社区管理员
  • 沉默王二
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧