深入理解Java之裝箱與拆箱

衍方 發表於 2020-09-14

一、Java資料型別

1、在說裝箱與拆箱之前,先說一下Java的基本資料型別,Java從資料型別上可以劃分為值型別引用型別,值型別是四類八種,分別是:

  • 整數型:byte̵,short̵,int̵,long
  • 浮點型:float,double
  • 字元型:char
  • 布林型:boolean
資料型別 記憶體 預設值 包裝類
byte 8位 0 Byte
short 16位 0 short
int 32位 0 Integer
long 64位 0L或0l Long
float 32位 0.0F或0.0f Float
double 64位 0.0D或0.0d Double
char 16位 \u0000 Character
boolean 8位 flase Boolean

2、引用型別:

  • 陣列
  • 類(class)
  • 介面(Interface)
  • 列舉(enum)

深入理解Java之裝箱與拆箱

3、值型別與引用型別的區別

  1. 從概念方面上來說:
    • 值型別:變數名指向具體的值
    • 引用型別:變數名指向資料物件的記憶體地址
  2. 從記憶體構建方面上來說:
    • 值型別:變數在宣告之後,Java就會立刻分配給它記憶體空間
    • 引用型別:它以特殊的方式(類似C指標)指向物件實體,這類變數宣告時不會分配記憶體,只是儲存
  3. 從使用方面上來說:
    • 值型別:使用時需要賦具體值,判斷時用 ” == “號
    • 引用型別:使用時可以賦null,判斷時使用 equals 方法

二、Java資料型別轉換

1、自動轉換

  • 定義:程式在執行過程中“悄然”進行的轉換,不需要使用者提前宣告,一般是從位數低的型別向位數高的型別轉換

  • 優先關係:按從低到高的順序轉換。不同型別資料間的優先

    關係如下:

    • 低--------------------------------------------->高
    • byte,short,char-> int -> long -> float -> double
  • 轉換規則:

    運算中,不同型別的資料先轉化為同一型別,然後進行運算

    運算元1型別 運算元2型別 轉換後的型別
    byte、short、char int int
    byte、short、char、int long long
    byte、short、char、int、long float float
    byte、short、char、int、long、float double double

2、強制轉換

  • 定義:強制型別轉換則必須在程式碼中宣告,轉換順序不受限制

  • 格式:在需要轉型的資料前加上“( )”,然後在括號內加入需要轉化的資料型別

  • 結果:精度可能會丟失,也可能更加精確

    int x;
    double y;
    
    x = (int)3.14 + (int)5.20  //精度丟失
    y = (double)x + (double)8  //精度提升
    
    輸出:x = 8;y = 16.0
    

三、Java之裝箱與拆箱

1、包裝類

  • Java是面嚮物件語言,號稱萬事萬物皆物件,因此,8種基本資料型別有了對應的類,這就是包裝類

2、什麼是裝箱與拆箱

  • 裝箱:將值型別裝換成引用型別的過程

  • 拆箱:將引用型別轉換成值型別的過程

  • 自動裝箱:

    int x = 3;
    Integer y = x;  //int --> Integer,Integer y = x <==> Integer y = Integer.valueOf(x)
    
  • 自動拆箱:

    Integer x = new Integer(5);
    int y = x;  //Integer --> int,int y = x <==> int y = x.intValue()
    

3、裝箱和拆箱是如何實現的

  • 裝箱過程是通過呼叫包裝器的valueOf方法實現的
  • 拆箱過程是通過呼叫包裝器的 xxxValue方法實現的。(xxx代表對應的基本資料型別)

4、注意點:

  1. 大量使用自動拆裝箱會使效能降低,還會造成大量的記憶體消耗
  2. 在過載方法中,可能出現問題
    List<Integer> list = new ArrayList<>();
    Integer x,y,z;
    x = 1;y = 2;z = 4;
    list.add(x);list.add(y);list.add(z);
    
    list.remove(2);
    

深入理解Java之裝箱與拆箱

在上面這段程式碼中ArrayList.remove方法有兩個過載方法,那麼list.remove(2)是呼叫了哪個方法,remove掉的是值為2的物件,還是remove了index為2,值為4的那個物件呢?

在這種情況下,編譯器不會進行自動拆裝箱,所以呼叫的是remove(int index),index為2值為4的這個Integer物件會被remove.

如果要呼叫 remove(Object o)的方法,應該這麼寫 list.remove(y)

  1. 快取值問題
  • 案例解析:

    Integer i1 = 100;
    Integer i2 = 100;
    Integer i3 = 200;
    Integer i4 = 200;
    System.out.println(i1==i2);
    System.out.println(i3==i4);
    
    Output: true false
    
  • 觀察原始碼:

    Intteger.valueOf方法

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

    IntegerCache類

    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) {
                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++);
    
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
    
        private IntegerCache() {}
    }
    

    從原始碼可以看出,在通過valueOf方法建立Integer物件的時候,如果數值在[-128,127]之間,便返回指向IntegerCache.cache中已經存在的物件的引用;否則建立一個新的Integer物件

  • Byte、Short、Integer、Long四種包裝類預設建立了數值為[-128,127]的相應型別的快取資料,但是超出此範圍仍會建立新的物件。

  • Character預設會建立[0,127]的響應型別的快取資料

  • 兩種浮點型沒有實現常量池技術,在某個範圍內的整型數值的個數是有限的,而浮點數卻不是

    包裝類 常量池 常量池範圍
    Byte 存在 [-128,127]
    Short 存在 [-128,127]
    Integer 存在 [-128,127]
    Long 存在 [-128,127]
    Character 存在 [0,127]
    Float 不存在
    Double 不存在
  • 注意點:

    • 當 "=="運算子的兩個運算元都是 包裝器型別的引用,則是比較指向的是否是同一個物件,而如果其中有一個運算元是表示式(即包含算術運算)則比較的是數值(即會觸發自動拆箱的過程)
    • 對於包裝器型別,equals方法並不會進行型別轉換
    • 算術運算會觸發裝箱與拆箱過程

文章為原創,轉載請宣告出處