算法#06--基本數據類型和包裝

基本數據類型

數據類型是程序設計語言描述事物、對象的方法。Java數據類型分爲內置類型和擴展類型兩大類。內置類型就是Java語言本身提供的基本數據類型,比如,整型數,浮點數,字符,布爾值等等。而擴展類型則是Java語言根據基本類型擴展出的其他類型,Java要求所有的擴展類型都必須包括在類定義裏面,這就是Java爲什麼是面向對象編程語言的原因。

內置類型

內置類型也稱作基本類型(Primitive Types),是其他類型的基礎。所有的其他類型(包括Java核心庫和用戶自定義類型)都是通過基本類型擴展而來的。

Java語言提供了八種基本類型。六種數字類型(四個整數型,兩個浮點型),一種字符類型,還有一種布爾型。如下:

浮點數

浮點值與我們通常所使用的小數是不同的。在使用中,它往往是難以確定的。常見的問題是定義了一個浮點數,經過一系列的計算,在數學上,它本來應該等於某個確定值 。

但是使用相等運算符(==)進行判斷時,往往得出的結論是不等;或者本來應該大於/小於某個特定值,結果卻可能剛好相反。因此,對於浮點值比較時,不能簡單地使用==, >, <等運算符。

我們做計算時,往往都有一個精度,或由於客觀事實,或由於我們的需要。這樣我們比較兩個浮點數時,就不是簡單的使用比較運算符了,而是在允許的精度之內作比較。比如:比較兩個浮點數(f1, f2)是否相等,精度爲p

| f1 - f2 | < p   ----------  ''true''
| f1 - f2 | > p   ----------  ''false''

上面的||表示絕對值運算,java.lang.Math類對絕對值運算提供了支持。 同樣,比較f1是否大於f2,如下:

( f1 - p ) > f2   ----------  ''true''

這種方法適用於大多數數據處理情況,但也有時候,必須是完全精確計算,這樣就不能使用這種方法了,而應該採用java.math.BigDecimal和java.math.BigInteger來計算。如財務算賬

包裝類

包裝器類有兩個主要的目的:

  1. 提供一種機制,將基本值“包裝”到對象中,從而使基本值能夠包含在爲對象而保留的操作中,比如添加到Collections 中,或者從帶對象返回值的方法中返回。注意,java5增加了自動裝箱和拆箱,程序員過去需手工執行的許多包裝操作,現在可以由java自動處理了。

  2. 爲基本值提供分類功能。這些功能大多數於各種轉換有關:在基本值和String對象間相互轉換,在基本值和String對象之間按不同基數轉換,如二進制、八進制和十六進制。

裝箱和拆箱

定義

在Java SE5之前,如果要生成一個數值爲10的Integer對象,必須這樣進行:

Integer i = new Integer(100);

而在從Java SE5開始就提供了自動裝箱的特性,如果要生成一個數值爲10的Integer對象,只需要這樣就可以了:

int i = 100;

這個過程中會自動根據數值創建對應的 Integer對象,這就是裝箱。

簡單一點說,裝箱就是自動將基本數據類型轉換爲包裝器類型;拆箱就是自動將包裝器類型轉換爲基本數據類型。

Integer i = 10; //裝箱
int index = i;  //拆箱

實現機制

我們就以Interger類爲例,下面看一段代碼:

public static void main(String[] args){
    Integer i = 10; //裝箱
    int index = i;  //拆箱
}

反編譯class文件之後得到如下內容:

從反編譯得到的字節碼內容可以看出,在裝箱的時候自動調用的是Integer的valueOf(int)方法。而在拆箱的時候自動調用的是Integer的intValue方法。其他的也類似,比如Double、Character。

因此可以用一句話總結裝箱和拆箱的實現過程:

裝箱過程是通過調用包裝器的valueOf方法實現的,而拆箱過程是通過調用包裝器的xxxValue方法實現的。(xxx代表對應的基本數據類型)。

常見面試問題

  • 下面這段代碼的輸出結果是什麼?
public class Main { 
      public static void main(String[] args) {
       Integer i1 = 100;
       Integer i2 = 100;
       Integer i3 = 200;
       Integer i4 = 200;

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

也許有些朋友會說都會輸出false,或者也有朋友會說都會輸出true。這裏注意“==”和“equal”的區別:

結果:
true
false

爲什麼會出現這樣的結果?輸出結果表明 i1 和 i2 指向的是同一個對象,而 i3 和 i4 指向的是不同的對象。此時只需一看源碼便知究竟,下面這段代碼是Integer的valueOf方法的具體實現:

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

從這2段代碼可以看出,在通過valueOf方法創建Integer對象的時候,如果數值在 [-128,127] 之間,便返回指向IntegerCache.cache中已經存在的對象的引用;否則創建一個新的Integer對象。

上面的代碼中 i1 和 i2 的數值爲100,因此會直接從cache中取已經存在的對象,所以 i1 和 i2 指向的是同一個對象,而 i3 和 i4 則是分別指向不同的對象。

  • 下面這段代碼的輸出結果是什麼?
public class Main {
    public static void main(String[] args) {
    Double i1 = 100.0;
    Double i2 = 100.0;
    Double i3 = 200.0;
    Double i4 = 200.0;

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

也許有的朋友會認爲跟上面一道題目的輸出結果相同,但是事實上卻不是。

實際輸出結果爲:
false
false

至於具體爲什麼,讀者可以去查看Double類的valueOf的實現。

在這裏只解釋一下爲什麼Double類的valueOf方法會採用與Integer類的valueOf方法不同的實現。很簡單:在某個範圍內的整型數值的個數是有限的,而浮點數卻不是。

注意,Integer、Short、Byte、Character、Long這幾個類的valueOf方法的實現是類似的,Double、Float的valueOf方法的實現是類似的。

  • 下面這段代碼的輸出結果是什麼?
public class Main {
    public static void main(String[] args) {
        Boolean i1 = false;
        Boolean i2 = false;
        Boolean i3 = true;
        Boolean i4 = true;

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

輸出結果爲:
true
true

至於爲什麼是這個結果,同樣地,看了Boolean類的源碼也會一目瞭然。下面是Boolean的valueOf方法的具體實現:

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

至於TRUE和FALSE的定義:

/**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code true}.
     */
    public static final Boolean TRUE = new Boolean(true);
    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);
  • 談談Integer i = new Integer(xxx)和Integer i =xxx;這兩種方式的區別。

1)第一種方式不會觸發自動裝箱的過程;而第二種方式會觸發;

2)在執行效率和資源佔用上的區別。第二種方式的執行效率和資源佔用在一般性情況下要優於第一種情況(注意這並不是絕對的)。

  • 下面這段代碼的輸出結果是什麼?
public class Main {
    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;

    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));
    System.out.println(g.equals(a+h));
    }
}

先別看輸出結果,讀者自己想一下這段代碼的輸出結果是什麼。這裏面需要注意的是:當 “==” 運算符的兩個操作數都是 包裝器類型的引用,則是比較指向的是否是同一個對象,而如果其中有一個操作數是表達式(即包含算術運算)則比較的是數值(即會觸發自動拆箱的過程)。另外,對於包裝器類型,equals方法並不會進行類型轉換。明白了這2點之後,上面的輸出結果便一目瞭然:

結果:
true
false
true
true
true
false
true

第一個和第二個輸出結果沒有什麼疑問。

第三句由於 a+b 包含了算術運算,因此會觸發自動拆箱過程(會調用intValue方法),因此它們比較的是數值是否相等。而對於c.equals(a+b)會先觸發自動拆箱過程,再觸發自動裝箱過程,也就是說a+b,會先各自調用intValue方法,得到了加法運算後的數值之後,便調用Integer.valueOf方法,再進行equals比較。

同理對於後面的也是這樣,不過要注意倒數第二個和最後一個輸出的結果(如果數值是int類型的,裝箱過程調用的是Integer.valueOf;如果是long類型的,裝箱調用的Long.valueOf方法)。

發佈了45 篇原創文章 · 獲贊 21 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章