天天看點

7.6、類型推導

這篇Java教程基于JDK1.8。教程中的示例和實踐不會使用未來發行版中的優化建議。
           

類型推導

類型推導是Java編譯器提供的用來檢視方法調用與相應方法聲明以确定參數(或多個參數)類型的一種能力。推理算法确定參數的類型,如果可用,還确定傳回結果的類型。最後,推理算法嘗試找到與所有參數一起工作最合适的類型。

為了說明這最後一點,在下面的例子中,推論确定傳遞給pick方法的第二個參數是Serializable類型:

static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());
           
泛型方法的類型推導

泛型方法已經介紹了類型推導,它使你能夠像調用普通方法一樣調用泛型方法,而不需要在尖括号之間指定類型。考慮下面的示例,BoxDemo,它需要Box類:

public class BoxDemo {

  public static <U> void addBox(U u, 
      java.util.List<Box<U>> boxes) {
    Box<U> box = new Box<>();
    box.set(u);
    boxes.add(box);
  }

  public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
    int counter = 0;
    for (Box<U> box: boxes) {
      U boxContents = box.get();
      System.out.println("Box #" + counter + " contains [" +
             boxContents.toString() + "]");
      counter++;
    }
  }

  public static void main(String[] args) {
    java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
      new java.util.ArrayList<>();
    BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
    BoxDemo.outputBoxes(listOfIntegerBoxes);
  }
}
           

下面是示例的輸出結果:

Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]
           

泛型方法addBox 定義了一個名為 U 的類型參數。通常,Java編譯器能通過泛型方法調用來推導出類型參數。是以,在大部分情況下,不需要指定其類型。比如:調用泛型方法addBox,可以通過類型見證來指定類型參數:

顯然,當你忽略類型見證,Java編譯器可以自動推導出類型參數為 Integer:

泛型類執行個體化的類型推導

隻要編譯器能夠從上下文推斷類型參數,就可以用一組類型參數(<>)來替換調用泛型類構造函數所需的類型參數。這對尖括号被非正式地稱為菱形。

考慮下面的類型聲明:

你可以用一組空的類型參數(<>)替換構造函數的參數化類型:

為使用類執行個體化的類型推導,必須使用空的類型參數(<>)。下面的例子将,編譯器會生成一個未檢查轉換異常因為構造器 HashMap() 指向 HashMap 的原始類型,而不是 Map<String,List<String>> 類型:

泛型類和非泛型類的泛型構造函數的類型推導

注意,構造函數在泛型和非泛型類中都可以是泛型的(換句話說,聲明它們自己的形式類型參數)。考慮下面的例子:

class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}
           

考慮類 MyClass 的執行個體化:

該語句建立參數化類型MyClass<Integer>的執行個體;該語句顯式地為泛型類MyClass<X>形式類型參數X指定整數類型。注意,這個泛型類的構造函數包含一個正式類型參數T。編譯器推斷這個泛型類構造函數類型參數T的類型為String類型(因為這個構造函數的實際參數是一個String對象)。

Java SE 7之前版本的編譯器能夠推斷出泛型構造函數的實際類型參數,類似于泛型方法。但是,如果使用<>, Java SE 7和更高版本中的編譯器可以推斷正在執行個體化的泛型類的實際類型參數。考慮下面的例子:

在本例中,編譯器推斷泛型類MyClass<X>的形式類型參數X的類型為Integer。它為這個泛型類的構造函數的形式類型參數T推斷為String。

注意: 需要注意的是,推理算法僅使用調用參數、目标類型,可能還使用明顯的預期傳回類型來推斷類型。推理算法不使用程式後面的結果。

目标類型

Java編譯器利用目标類型推斷泛型方法調用的類型參數。表達式的目标類型是Java編譯器期望的資料類型,這取決于表達式出現的位置。考慮Collections.emptyList ,聲明如下:

考慮下面的指派語句:

該語句期望List<String>;這個資料類型就是目标類型。因為方法emptyList傳回類型為List<T>的值,是以編譯器推斷類型參數T必須是值字元串。這在Java SE 7和8中都适用。或者,你可以使用類型見證,并指定T的值如下:

但是,在這種情況下沒有必要這樣做。但在其他情況下,這是必要的。考慮以下方法:

void processStringList(List<String> stringList) {
    // process stringList
}
           

假設你想用一個空清單調用方法processStringList 。在Java SE 7中,下列語句不能編譯:

Java SE 7編譯器生成的錯誤消息類似如下:

編譯器需要類型參數T的值,是以它從value對象開始。Collections.emptyList 的調用傳回一個類型為List<Object>的值,該值與processStringList 方法不相容。是以,在Java SE 7中,你必須如下所示指定類型參數的值:

在Java SE 8中不再需要這樣做。目标類型的概念已經擴充為包含方法參數,例如processStringList方法的參數。在本例中,processStringList需要類型為List<String>的參數。Collections.emptyList() 傳回的值是List<T>,是以使用List<String>的目标類型,編譯器推斷類型參數T的值是String。是以,在Java SE 8中,編譯以下語句:

下一篇:通配符