5分鐘完全理解-Java自動裝箱、拆箱

什麼是自動裝箱,拆箱

先拋出定義,Java中基礎數據類型與它們的包裝類進行運算時,編譯器會自動幫咱們進行轉換,轉換過程對程序員是透明的,這就是裝箱和拆箱,裝箱和拆箱可讓咱們的代碼更簡潔易懂

Java中基礎數據類型與它們對應的包裝類見下表(共8種):java

原始類型 包裝類型
boolean Boolean
byte Byte
char Character
float Float
int Integer
long Long
short Short
double Double

當表格中左邊列出的基礎類型與它們的包裝類有以下幾種狀況時,編譯器會自動幫咱們進行裝箱或拆箱.程序員

  • 進行 = 賦值操做(裝箱或拆箱)
  • 進行+,-,*,/混合運算 (拆箱)
  • 進行>,<,==比較運算(拆箱)
  • 調用equals進行比較(裝箱)
  • ArrayList,HashMap等集合類 添加基礎類型數據時(裝箱)

咱們看一段日常很常見的代碼面試

public void testAutoBox() {
    List<Float> list = new ArrayList<>();
    list.add(1.0f);
    float firstElement = list.get(0);
}
複製代碼

list集合存儲的是Float包裝類型,我傳入的是float基礎類型,因此須要進行裝箱,而最後的get方法返回的是Float包裝類型,咱們賦值給float基礎類型,因此須要進行拆箱,很簡單,安排的明明白白bash

具體自動裝箱,拆箱,代碼是如何實現的

既然編譯器幫咱們自動進行了裝箱,拆箱,那麼編譯器到底作了些什麼,要搞清楚這些,最簡單直接的方式就是看類通過編譯器編譯後的字節碼,下面是上面一段代碼的字節碼實現ui

public testAutoBox()V
   L0
    LINENUMBER 15 L0
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 16 L1
    ALOAD 1
    FCONST_1
    INVOKESTATIC java/lang/Float.valueOf (F)Ljava/lang/Float;
    INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;)Z
    POP
   L2
    LINENUMBER 17 L2
    ALOAD 1
    ICONST_0
    INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object;
    CHECKCAST java/lang/Float
    INVOKEVIRTUAL java/lang/Float.floatValue ()F
    FSTORE 2
   L3
    LINENUMBER 18 L3
    RETURN
複製代碼
  • L0,對應咱們代碼的第一行,new了一個ArrayList,並賦值給了1號引用(就是list)。
  • L1,先加載list到棧頂,而後FCONST_1指令就是從常量池加載1.0f浮點數並壓入棧頂(這一塊知識,見附錄1),而後調用了Float類的靜態 valueOf方法,進行裝箱
    ,而後調用list的add方法。
  • L2,先加載list到棧頂,從常量池獲取0(float,int,long,double等基礎類型初始值都是0),調用list的get方法,檢查是否能轉換,調用了Float的floatValue方法,進行拆箱
    ,存儲獲得的浮點數。

###因此結果很明顯了,以float和Float爲例,裝箱就是調用Float的valueOf方法new一個Float並賦值,拆箱就是調用Float對象的floatValue方法並賦值返回給float。其餘基礎類型都是大同小異的,具體能夠查看源碼。spa

##自動裝箱、拆箱中的坑 ###面試題中常常會有考點就是考察面試者對Java中自動裝箱、拆箱是否瞭解透徹,好比下面這一道面試題?指針

public void testAutoBox2() {
	 //1
     int a = 100;
     Integer b = 100;
     System.out.println(a == b);
     
     //2
     Integer c = 100;
     Integer d = 100;
     System.out.println(c == d);
     
     //3   
     c = 200;
     d = 200;
     System.out.println(c == d);
}
複製代碼

請問執行結果是多少?
code

題目很常見啦,客官別見笑,咱們來分析一下,orm

  • 第1段代碼,基礎類型a與包裝類b進行==比較,這時b會拆箱,直接比較值,因此會打印 true
  • 第2段代碼,二個包裝類型,都被賦值了100,因此根據咱們以前的解析,這時會進行裝箱,調用Integer的valueOf方法,生成2個Integer對象,引用類型==比較,直接比較對象指針,這裏咱們先給出結論,最後會分析緣由,打印 true
  • 跟上面第2段代碼相似,只不過賦值變成了200,直接說結論,打印 false

結果是否是很詭異,咱們直接去看Integer類valueOf方法的實現(JDK8的實現)cdn

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
複製代碼

能夠看到,這裏的實現並非簡單的new Integer,而是用IntegerCache作一個cache,cache的range是能夠配置的

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);
         // Maximum array size is Integer.MAX_VALUE
         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++);
  ....
複製代碼

這是IntegerCache靜態代碼塊中的一段,默認Integer cache 的下限是-128,上限默認127,能夠配置,因此到這裏就清楚了,咱們上面當賦值100給Integer時,恰好在這個range內,因此從cache中取對應的Integer並返回,因此二次返回的是同一個對象,因此==比較是相等的,當賦值200給Integer時,不在cache 的範圍內,因此會new Integer並返回,固然==比較的結果是不相等的。

###以上

附錄1:JVM字節碼整型的入棧指令有4個,分別是:

  • iconst(0~5分別對應iconst_0、iconst_一、iconst_二、iconst_三、iconst_四、iconst_5,-1對應iconst_m1)
  • bipush (-128~127)
  • sipush (-32768~32767)
  • ldc (-2147483648~2147483647)
相關文章
相關標籤/搜索