某某茶叶有限公司欢迎您!
金沙棋牌在线 > 金沙棋牌在线 > Java学习之自动装箱和自动拆箱源码分析

Java学习之自动装箱和自动拆箱源码分析

时间:2019-12-29 06:38

自动装箱(boxing)和自动拆箱(unboxing)

首先了解下Java的四类八种基本数据类型

基本类型 占用空间(Byte) 表示范围 包装器类型

boolean

1/8

true|false

Boolean

char

2

-128~127

Character

byte

1

-128~127

Byte

short

2

-2ˆ15~2ˆ15-1

Short

int

4

-2ˆ31~2ˆ31-1

Integer

long

8

-2ˆ63~2ˆ63-1

Long

float

4

-3.403E38~3.403E38

Float

double

8

-1.798E308~1.798E308

Double

Java自动装箱与拆箱的分析

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

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

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


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

public class AutoBoxing2
{
    public static void main(String[] args)
    {
        Integer a = 1;
        Integer b = 2;
        Integer c = a+b;
    }
}

??反编译结果为:(如果对于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."":()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

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


??接下来我们就可以上面题目中给出的 System.out.println(c==d);和System.out.println(e==f);他们分别的结果为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() {}
    }

??可以看到实际上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);

??那么输出的结果是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));

??反编译之后:

          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    

??可以看到实际在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

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

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

??通过查看源码可知此条语句的输出结果为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;
    }

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

 

 

??自动装箱与拆箱机制在实际使用中非常常见,不过也特别容易出错,博主在面对下面一道题的时候自信满满,可...

一、定义

自动装箱和自动拆箱,是从javaSE5.0开始添加的

  1. 自动装箱

把基本类型用它们对应的引用类型包装起来,使它们具有对象的特质,可以调用toString()、hashCode()、getClass()、equals()等方法。
如:Integer a=100; 实际上,执行上面那句代码的时候,系统为我们执行了:Integer i = Integer.valueOf(100); 返回一个Integer对象。

  1. 自动拆箱

与自动装箱相反,把包装类型转换为基本数据类型,如:int a = new Integer(100); 编译器内部会调用int intValue()返回该Integer对象的int值。

  1. 注意:

自动装箱和拆箱是由编译器来完成的,编译器会在编译期根据语法决定是否进行装箱和拆箱动作。

1.装箱:自动将基本数据类型转换为包装器类型 拆箱:将包装器类型转化为基本数据类型

自动装箱

Java中所谓的装箱通俗点就是:八种基本数据类型在某些条件下使用时,会自动变为对应的包装器类型。

如下清单1:

@Test
public void boxingTest() {

Integer i1 = 17;
Integer i2 = 17;

Integer i3 = 137;
Integer i4 = 137;

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

输出:

true
false

解释下清单1第11句输出true的原因:

当包装器类型进行“==”比较时,i3会调用Integer.valueOf自动装箱基本数据类型为包装器类型。

/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

从源码中可以看出,Integer对象自动缓存int值范围在low~high(-128~127),如果超出这个范围则会自动装箱为包装类。

Note:

  1. Integer、Short、Byte、Character、Long这几个包装类的valueOf方法的实现是类似的;
  2. Double、Float的valueOf方法的实现是类似的。
  3. Boolean的valueOf方法的实现是个三目运算,形如`  return (b ? TRUE : FALSE);  `

二、例子

通过下面的例子了解自动装箱

Integer a = 1000, b = 1000;
Integer c = 100, d = 100;

System.out.println(a == b);
System.out.println(c == d);

//程序输出结果
//false,true
  1. 对于例子的结果,我们先看看Integer装箱的源码
/**
当没有使用new Integer创建对象,而直接赋值Integer a=100时,
通常应优先使用该方法,而不是构造方法 Integer(int),
因为该方法有可能通过缓存经常请求的值而显著提高空间和时间性能。 
当值超过缓存的最大值IntegerCache.high时,就会返回新的Integer实例。
*/
public static Integer valueOf(int i) {
    //assert:java断言用于在代码中捕捉这些假设,
    //可以将断言看作是异常处理的一种高级形式,
    //详情百科: https://baike.baidu.com/item/assert/10931289?fr=aladdin
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
  1. IntegerCache类的实现
/**
缓存支持值之间的128和127(含127)的自动装箱的对象标识的语义按JLS(Java Language Specification java语言规范)。

这个缓存在第一次使用时,就进行初始化;
缓存的大小可设置jdk的 -XX:AutoBoxCacheMax=<size>(即通过设置AutoBoxCacheMax改变缓存上界的大小来改变缓存的大小)

*/
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;
            //在虚拟机的初始化时,high属性可设置并保存在sun.misc.vm类的私有系统属性中。
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
            // 通过解码integerCacheHighPropValue,而得到一个候选的上界值  
                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() {}
    }
  1. 通过查看源码,可以知道Integer在装箱时,自动缓存了-128~127的值;所以,当超过缓存的值大小时,就会创建新的Integer对象,则在两个Integer对象使用==比较时,就会为false

通过下面的例子了解自动拆箱

Integer a = new Integer(1000);  
int b = 1000;  
Integer c = new Integer(10);  
Integer d = new Integer(10);

System.out.println(a == b);  
System.out.println(c == d);  


//程序输出
// true  、false 
//Integer对象与int比较时,会先拆箱(即调用intValue()方法,返回Integer对象的int值),再比较。

基本数据类型  包装器类型

自动拆箱

Java中所谓的拆箱通俗点就是:八种包装器类型在某些条件下使用时,会自动变为对应的基本数据类型。

清单2:

@Test
public void unboxingTest() {
Integer i1 = 17;
int i2 = 17;

int i3 = 137;
Integer i4 = 137;

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

}

输出:

true
true

解释下清单2第10句输出true的原因:

当程序执行到第10句时,i4会调用Integer.intValue方法自动拆箱包装器类型为基本数据类型。

/**
* Returns the value of this {@code Integer} as an
* {@code int}.
*/
public int intValue() {
return value;
}

从源码可以看出,当包装器类型和基本数据类型进行“==”比较时,包装器类型会自动拆箱为基本数据类型。

清单3内容如下:

@Test
public void unboxingTest() {
Integer i1 = 17;
Integer i2 = 17;

Integer i3 = 137;
Integer i4 = 137;

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

// equals
System.out.println(i1.equals(i2));
15 System.out.println(i3.equals(i4));

}

输出:

true
false
true
true

解释第15句为什么会输出true:

因为在Integer包装类实现的equals方法中,只要比较的当前对象是Integer实例,那么就会自动拆箱为基本数据类型。从以下Integer类的equals方法的源码就可看出:

/**
* Compares this object to the specified object. The result is
* {@code true} if and only if the argument is not
* {@code null} and is an {@code Integer} object that
* contains the same {@code int} value as this object.
*
* @param obj the object to compare with.
* @return {@code true} if the objects are the same;
* {@code false} otherwise.
*/
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}

Note:

  1. Integer、Short、Byte、Character、Long这几个包装类的intValue方法的实现是类似的;
  2. Double、Float的intValue方法的实现是类似的。
  3. Boolean的booleanValue方法的实现和intValue方法的实现也是类似的。

装箱拆箱综合清单:

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;
Long h = 2L;

// 会自动拆箱(会调用intValue方法)
System.out.println(c==d);
// 会自动拆箱后再自动装箱
System.out.println(e==f);
// 虽然“==”比较的是引用的是否是同一对象,但这里有算术运算,如果该引用为包装器类型则会导致自动拆箱
System.out.println(c==(a+b));
// equals 比较的是引用的对象的内容(值)是否相等,但这里有算术运算,如果该引用为包装器类型则会导
 // 致自动拆箱,再自动装箱
// a+b触发自动拆箱得到值后,再自动装箱与c比较
System.out.println(c.equals(a+b));
// 首先a+b触发自动拆箱后值为int型,所以比较的是值是否相等
System.out.println(g==(a+b));
// 首先a+b触发自动拆箱后值为int型,自动装箱后为Integer型,然后g为Long型
System.out.println(g.equals(a+b));
// 首先a+h触发自动拆箱后值为long型,因为int型的a会自动转型为long型的g然后自动装箱后为Long型,
 // 而g也为Long型
System.out.println(g.equals(a+h));

}

输出:

true
false
true
true
true
false
true

 这里面需要注意的是:金沙棋牌在线,当 “==”运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)另外,对于包装器类型,equals方法并不会进行类型转换。

总结:

  1. 在使用装箱类时,推荐使用valueOf方法创建实例来提高性能
  2. 两个Integer对象最好不要用==比较。因为:-128~127范围(一般是这个范围)内是取缓存内对象用,所以相等,该范围外是两个不同对象引用比较,所以不等。
  3. Integer和 int之间可以进行各种比较;Integer对象将自动拆箱后与int值比较

int(4bytes)    Integer

详细参考

http://blog.csdn.net/jackiehff/article/details/8509056

byte(1byte)    Byte