int和Integer的区别及自动装箱拆箱原理

weblog 精帖
1634

int和Integer的区别

说起int和Integer的区别大家耳熟能详的是:

  1. int是java中的基本数据类型,而Integer是引用类型。
  2. Integer必须实例化后才能使用,而int不需要。
  3. int的默认值是0,而Integer的默认值是null。

然而仅仅知道这些,对其两者的了解还是远远不够的。那么下面就继续探索。

语法糖的味道-自动装箱和拆箱的原理

        什么是自动装箱和自动拆箱呢?定义:Java中基础数据类型与它们的包装类进行运算时,编译器会自动帮我们进行转换,转换过程对程序员是透明的,这就是装箱和拆箱,装箱和拆箱可以让我们的代码更简洁易懂。

换句话说,假如我们代码中定义了一个变量

Integer i=100;

虽然我们代码中是这么写的,但是经过java编译器编译以后它的含义就变了,就会变成下边这样

Integer i=Integer.valueOf(100);

不信的话写行代码验证一下,如下代码:

public class Test{
	public static void main(String[] args){
		Integer i=200;
	}
}

经过反编译以后:

Compiled from "Test.java"
public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: sipush        200
       3: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       6: astore_1
       7: return
}

看反编译出来的字节码中有这么一行:

3: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

它的含义就是在调用Integer.valueOf()方法。

以上就是自动装箱的原理。自动拆箱的原理其实和装箱差不多,自动拆箱调用的是Integer.intValue()方法。例如如下方法:

	public void test(Integer i){
		int j=i;
	}

反编译过后

  public void test(java.lang.Integer);
    Code:
       0: aload_1
       1: invokevirtual #2                  // Method java/lang/Integer.intValue:()I
       4: istore_2
       5: return

valueOf()方法中的坑

我们来看源码:

/**
 * This method will always cache values in the range -128 to 127,
 */  
public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

看了源码才知道,原来java中对于Integer类型并且是定义在-128到127之间的数会进行缓存。这就解释了我们下边要讨论的问题。

讨论

Integer i=new Integer(1);
Integer j=new Integer(1);
System.out.println(i==j);//false

无容置疑,上面的代码一定会打印false,因为对于两个新new的对象而言地址是永远不能相等的。

Integer i=new Integer(1);
Integer j=1;
System.out.println(i==j);//false

        没错,上面的代码还会返回false,因为i变量是new出来的,在对内存中存在,而 j 变量根据我们之前说的,会自动装箱,而且-128到127之间的数值会缓存在常量池中,所以两者地址是不相同的。

Integer i=new Integer(1);
int j=1;
System.out.println(i==j);//true

        但是把Integer变成int后,如上边代码,就会返回true了,因为Integer类型的变量在和int类型的变量比较时,Integer类型的变量会先自动拆箱成int,再做比较,所以就是两个int类型的变量比较,当然会返回true了。不相信的话就反编译字节码

  public void test();
    Code:
       0: iconst_1
       1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       4: astore_1
       5: iconst_1
       6: istore_2
       7: aload_1
       8: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
      11: iload_2
      12: if_icmpne     19
      15: iconst_1
      16: goto          20
      19: iconst_0
      20: istore_3
      21: return

看第8行字节码就知道了。 

Integer i=127;
Integer j=127;

Integer a=128;
Integer b=128;
System.out.println(i==j);//true
System.out.println(a==b);//false

        上边的代码就有点意思了,不过也很简单。说会自动装箱时的缓存(-128到127之间的数值会缓存在常量池中),i和j会缓存再常量池中的同一个对象,而a和b时在堆中new了两个不同的对象,所以结果一目了然。

分析到此结束。

猜你喜欢