不簡單的基本資料型別

程式設計師大貓發表於2018-12-28

[toc]

資料型別的出現是為了把資料分成所需記憶體大小不同的資料,合理、有效利用記憶體。資料型別在計算機語言裡面,是對記憶體位置的一個抽象表達方式。

資料型別

在JAVA語言種將資料型別分為兩類:基本資料型別引用資料型別

資料型別

  • 基本型別:簡單資料型別是不能簡化的、內建的資料型別、由程式語言本身定義,它表示了真實的數字、字元和整數。
  • 引用資料型別:Java語言的引用資料型別包括類、介面和陣列型別,還有一種特殊的null型別。引用資料型別是對一個物件的引用,物件有兩種:陣列和例項(物件導向的基本知識點,這篇文章主要以基本資料型別為主)

基本資料型別

基本資料型別分為4類,分別為整型、浮點型、字元型和布林型

整型

Java從入門到精通

1位元組=8位

4種整型量都是有符號的,符號佔一位(byte的取值範圍本該是2的8次方,其他類似)。

Java整數常量預設是int型別

下表可以清楚的看出每個取值範圍內可以使用的資料型別(這裡沒有把負數部分畫出來)

取值範圍 0~127 128 ~ 32767 32768 ~ 2147483647 2147483648 ~ 9223372......
資料型別 byte、short、int、long short、int、long int、long long
  • byte、short可以使用在其取值範圍內的整數常量,系統自動把這些整數常量當成byte或short型別處理。使用超出範圍的整數常量會報錯。
  byte b = 127;
  byte b2 = 128;//報錯:Required:byte Found:int(預設是int型別)
複製程式碼
  • 如果把一個超出int取值範圍的整數常量賦值給long型別,會報錯;強制使用long型別需要在整數常量後加L(或l)
  • 如果把一個int範圍內的整數常量賦值給long型別,不會報錯,是因為發生了自動型別轉換(下面會具體說明)
 int num1 = 2147483647;
 long l1 = 2147483647;//正常,發生了自動型別轉換
 int num2 = 2147483648;//報錯: Integer number too
 large
 long l2 = 2147483648;//報錯: Integer number too
 large 
 long l3 = 2147483648L;//正常
 
複製程式碼

整數常量的四種表示方式

  1. 二進位制:0b或0B開頭(java7開始支援)
  2. 八進位制:0開頭
  3. 十進位制:不做過多說明
  4. 十六進位制:0x或0X開頭
int num1=013;//八進位制
int num2=0x13;//十六進位制
複製程式碼

重點說明二進位制表示方式

所有數字在計算機底層都是以二進位制形式表示的,原碼是直接將一個數值換算成二進位制數。計算機是以補碼形式儲存所有的整數。正數的補碼和原碼完全相同,負數的補碼是原碼的反碼加1;反碼是對原碼按位取反,最高位符號位不變。

Java瘋狂講義

  • 二進位制整數的最高位是符號位,1表示負數,0表示正數
  • java整數常量預設是int型別,所以二進位制整數預設佔32位,定義一個不足位的二進位制整數時,會自動高位補齊(補0),第32位是符號位
  • 系統對byte、short型別的使用與十進位制一致
  • 使用long型別需要在末尾加L(或l),表示佔64位,第64位是符號位

關於進位制間的轉換請看 進位制間的轉換 這篇文章

浮點型

Java從入門到精通

  • float(單精度浮點型):第1位是符號位,接下來8位表示指數,其餘23位表示尾數
  • double(雙精度浮點型):第1位符號位,接下來11位表示指數,其餘52位表示尾數

浮點數的兩種表示方式

  1. 十進位制數形式,float浮點數後加F(或f),double浮點數後加D(或d),浮點數必須包含一個小數點,

    float f=5.1200_001f;//可以使用_分隔,方便校對數位

  2. 科學計數法形式,只有浮點型的數值可以使用科學計數法形式表示。如:5.12E2(5.12✖️10^2)

字元型

  • java語言使用16位的Unicode字符集(關於字元編碼問題請看阮一峰的字元編碼筆記)作為編碼方式
  • char型別的取值範圍是0~2^16(65536),是無符號的整數(正數),可以被用作整數型別的值來使用,同樣系統也會把整數型別的值當成char型別處理

字元型常量的三種表示方式

  1. 直接通過單個字元來指定,如 'a','0'
  2. 通過轉義字元表示特殊字元型常量

常用轉義字元

Java從入門到精通

  1. 直接使用Unicode值表示字元型常量,範圍'\u0000'-'\uFFFF',其中前256個('\u0000'-'\u00FF')字元和ASCII碼錶中的字元安完成重合

布林型

用於表示邏輯上的”真“或”假“

基本型別的型別轉換

自動型別轉換

取值範圍小的型別賦值給取值範圍大的型別,系統自動進行型別轉換,如圖所示,可以根據箭頭指向發生自動轉換

自動轉換

強制型別轉換

強制型別轉換可能會丟失精度,所以需要使用()進行型別轉換。

Java瘋狂講義

32位int型最高位0表示正數,轉換為byte型別,高位精度丟失,第8位為byte最高位,1表示負數,因此正數的int型因丟失精度變成了負數的byte型。

表示式型別的自動提升

當一個算術表示式中包含多個基本型別的值時,整個算術表示式的資料型別將發生自動提升

  • byte、short、char自動提升為int型別
  • 整個表示式的資料型別自動提升到與表示式中最高等級運算元的資料型別
  • 與字串拼接規則:字串在+號的左邊,表示字串拼接,如果字串在+號的右邊要具體看左邊的情況
  System.out.println("Hello" + 'a' + 7);//Helloa7
  System.out.println('a' + 7 + "Hello");//104Hello
複製程式碼

包裝類

Java瘋狂講義

為什麼還需要包裝類

Java中的int型別在記憶體中只佔用4個位元組,而一個Object物件本身最少佔用8個位元組,另外還需要4個位元組來引用它。除此之外CPU對基本型別的處理更加高效。

既然如此為什麼還需要包裝類?

Java是物件導向的程式語言,但這8種基本資料型別並不支援面試物件的特性。所以 Java 需要一個這樣的包裝類來使其物件導向的完整性,這樣一來基本型別具有了物件的特性,例如:實現可空型別,完成字串像基本型別的轉換,另外Java 集合中只能放入包裝型別,不支援基本型別。

       //可為空
        Integer integer=null;
        //字串轉換為int型
        int num=Integer.valueOf("123");
        //java集合只能放入Integer型別,無法放入int型別
        ArrayList<Integer> arrayList=new ArrayList<>();
複製程式碼

緩衝池——減少記憶體開銷,提高程式效能

 Integer integer = Integer.valueOf(127);
 int intValue = integer.intValue();
複製程式碼
  • 通過靜態方法valueOf將基本型別包裝為Integer型別
  • 通過intValue方法將包裝類轉換為int型別。
    private final int value;
    //intValue方法比較簡單,直接返回value
    public int intValue() {
        return value;
    }
    //valueOf方法的內部實現
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
複製程式碼

可以從valueOf方法內部看到,傳入進來的i並不是直接呼叫new Integer(i)來建立物件的,而是通過IntegerCache.cache快取中獲取的。

IntegerCache是Integer的一個內部類

 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() {}
    }
複製程式碼

從上面的程式碼中,可以看到IntegerCache類中宣告瞭Integer cache[]陣列來快取Integer物件的,並且IntegerCache.lowIntegerCache.high常量確定了cache的範圍從-128~127,也就是說不在這個範圍內的值,會直接通過new Integer(i)來建立物件。

        Integer num1 = 127;
        Integer num2 = 127;
        System.out.println(num1 == num2);//引用的同一個物件所以輸出為 true
        
        Integer num1 = 128;
        Integer num2 = 128;
        System.out.println(num1 == num2);//超出了快取範圍輸出false 
        
        Integer integer1 = new Integer(127);
        Integer integer2 = new Integer(127);
        System.out.println(integer1 == integer2);//直接new的物件,未走快取輸出false
複製程式碼

在int和Integer型別之間轉換其實不需要如此的麻煩,從java5開始為我們提供了裝箱和拆箱的操作,簡化轉換過程。

自動裝箱、拆箱——只是一種語法糖

Integer a= 123;     // 裝箱
int b = a;         // 拆箱
複製程式碼

裝箱和拆箱可以看成一種java語法糖,javac編譯器會自動把裝箱轉換為Integer.valueOf(),把拆箱轉換為Integer.intValue(),因此我們在使用裝箱時依然可以使用Integer的快取。

成員變數value的不可變性

包裝類的成員變數value都使用了final關鍵字修飾,這表明它們是不可變型別(常量)。

對於成員變數value只需要明白3點就可以了

  1. 它的訪問修飾符是pirvate,所以外部無法修改它
  2. 使用了final關鍵字修飾,內部一旦賦值便無法再改變
  3. 基本資料型別的不可變性滿足上面兩點就可以了,不像引用型別,被final修改後,引用本身不可變,但具體的例項是可以通過引用改變的。這點在String型別中詳細說明(String是通過copyOf()方法來保證不可變的)

之所以設計成不可變的的目的是為了保證基本的資訊保安和併發環境下的執行緒安全

其他基本型別的包裝類

  • Boolean,快取了true/false對應例項
  • Short,快取範圍與Integer一致
  • Byte,全部快取
  • Character,快取範圍'\u0000' ~ '\u007F'
  • Double和Float型別沒有使用快取池

運算

將運算子和運算元連線在一起就形成了表示式

這裡只對兩種運算做簡要說明(其實主要是為了記錄下)

  • 邏輯運算子

圖片來源已忘記

//註釋
& :邏輯與 &&:短路與 
| :邏輯或 ||:短路或 
! :邏輯非  ^:邏輯異或
複製程式碼
  • 位運算子(直接對二進位制進行運算)

圖片來源已忘記

相關文章