关键词搜索

源码搜索 ×
×

Java自动装箱与拆箱

发布2016-03-04浏览2564次

详情内容


欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。


欢迎跳转到本文的原文链接:https://honeypps.com/java/java-decompilation/

  自动装箱与拆箱机制在实际使用中非常常见,不过也特别容易出错,博主在面对下面一道题的时候自信满满,可还是没有能够全对,所以写下这篇博文,给自己对自动装箱与拆箱机制做一下知识巩固,也给各位朋友做一下参考。
  首先有这样一道题,给出下面代码的输出结果:

public class AutoBoxing
{
    public static void main(String[] args)
    {
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;

        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
    }
}

      运行结果:

    true
    false
    true
    true
    true
    false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      如果你看到这边,答案都正确,而且没有丝毫的疑问,那么对于你来说这篇博文就此结束了,如果没有,请继续翻阅。


      首先从最基础的开始讲起,首先通过反编译来看一看自动装箱和拆箱的过程:
      首先看如下一段程序:

    public class AutoBoxing2
    {
        public static void main(String[] args)
        {
            Integer a = 1;
            Integer b = 2;
            Integer c = a+b;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      反编译结果为:(如果对于java反编译不太了解的朋友可以先看一下《通过Java反编译揭开一些问题的真相》)

    public jvm.optimize.AutoBoxing2();
        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 3: 0
    
      public static void main(java.lang.String[]);
        flags: ACC_PUBLIC, ACC_STATIC
    
        Code:
          stack=2, locals=4, args_size=1
             0: iconst_1      
             1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             4: astore_1      
             5: iconst_2      
             6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             9: astore_2      
            10: aload_1       
            11: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
            14: aload_2       
            15: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
            18: iadd          
            19: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
            22: astore_3      
            23: return        
          LineNumberTable:
            line 8: 0
            line 9: 5
            line 10: 10
            line 11: 23
    
      21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

      可以看到Integer a=1实际上自动装箱为Integer a = Integer.valueOf(1),而在进行a+b的时候可以看到进行了自动拆箱,将a拆箱为Integer.intValue();然后将a和b的int值相加,相加之后有进行了自动装箱:Integer c=Integer.valueOf(3).


      接下来我们就可以上面题目中给出的 System.out.println(cd);和System.out.println(ef);他们分别的结果为true和false。
      知道Integer会缓存-128至127的朋友估计这两条语句的输出结果都能答对。
      如果没有答对,请看解析:
      Integer c=3;会自动装箱为Integer c = Integer.valueOf(3),那么看一下valueOf方法的源码:

    public static Integer valueOf(int i) {
            assert IntegerCache.high >= 127;
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
        private static class IntegerCache {
            static final int low = -128;
            static final int high;
            static final Integer cache[];
    
            static {
                // high value may be configured by property
                int h = 127;
                String integerCacheHighPropValue =
                    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                if (integerCacheHighPropValue != null) {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                }
                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() {}
        }
    
      21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

      可以看到实际上Integer会缓存-128值127的内容,如果值在这个区间之内(比如c和d),那么就会返回IntegerCache中的引用,所以Integer c= Integer d = IntegerCache.cache[3+(–128)] = IntegerCache.cache[131], c和d是相等的。
      但是如果超过这个区间,比如e和f,则Integer e = new Integer(321); Integer f = new Integer(321);new出来的自然是在堆中新开辟的内存,两份地址不同,自然e和f不同,也就是如果遇到这样的情况:

    Integer m = new Integer(2);
    Integer n = new Integer(2);
    System.out.println(m==n);
    
    • 1
    • 2
    • 3

      那么输出的结果是false(如果Integer m=2; Intger n=2则m和n相同)


      接着再说System.out.println(c==(a+b));
      我们看如下代码:

    Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    System.out.println(c==(a+b));
    
    • 1
    • 2
    • 3
    • 4

      反编译之后:

              0: iconst_1      
             1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             4: astore_1      
             5: iconst_2      
             6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
             9: astore_2      
            10: iconst_3      
            11: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
            14: astore_3      
            15: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
            18: aload_3       
            19: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
            22: aload_1       
            23: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
            26: aload_2       
            27: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
            30: iadd          
            31: if_icmpne     38
            34: iconst_1      
            35: goto          39
            38: iconst_0      
            39: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
            42: return    
    
      21
    • 22
    • 23

      可以看到实际在c==(a+b)的时候是执行拆箱机制,实际上就是在运算3==2+1,当然就是true咯。


      继续说明: System.out.println(c.equals(a+b));
      同样看一下c.equals(a+b)反编译的结果(篇幅限制,只截取部分相关的结果):

            19: aload_1       
            20: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
            23: aload_2       
            24: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
            27: iadd          
            28: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
            31: invokevirtual #5                  // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

      可以看到a+b先拆箱为int再相加之后再装箱为Integer型与c进行equals比较,那么我们再看一下equals()方法的源码:

        public boolean equals(Object obj) {
            if (obj instanceof Integer) {
                return value == ((Integer)obj).intValue();
            }
            return false;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      通过查看源码可知此条语句的输出结果为true。


      最后来看一下System.out.println(g==(a+b));和System.out.println(g.equals(a+b));两条语句。
      System.out.println(g==(a+b));由前面的推论可知最后g拆箱为long型,a+b为int型,基础类型int可以自动升级为long,所以输出为true。
      对于System.out.println(g.equals(a+b));可以看一下Long的equals()方法。

        public boolean equals(Object obj) {
            if (obj instanceof Long) {
                return value == ((Long)obj).longValue();
            }
            return false;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      对于(a+b)来说是Integer类型,所以返回false.
      鉴于包装类的“==”运算在不遇到算术运算的情况下不会自动拆箱,以及它们equals()方法不处理数据转型的关系,博主建议在实际编码中要尽量避免这样使用自动装箱与拆箱机制。

    欢迎跳转到本文的原文链接:https://honeypps.com/java/java-decompilation/


    欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。


    相关技术文章

    点击QQ咨询
    开通会员
    返回顶部
    ×
    微信扫码支付
    微信扫码支付
    确定支付下载
    请使用微信描二维码支付
    ×

    提示信息

    ×

    选择支付方式

    • 微信支付
    • 支付宝付款
    确定支付下载