Java™ 教程(類型推斷)

類型推斷

類型推斷是Java編譯器查看每一個方法調用和相應聲明的能力,以肯定使調用適用的類型參數,推理算法肯定參數的類型,若是可用,還肯定分配或返回結果的類型,最後,推理算法嘗試查找適用於全部參數的最具體類型。java

爲了說明最後一點,在下面的示例中,推斷肯定傳遞給pick方法的第二個參數是Serializable類型:git

static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());

類型推斷和泛型方法

泛型方法向你介紹了類型推斷,它使你可以像普通方法同樣調用泛型方法,而無需在尖括號之間指定類型,考慮如下示例BoxDemo,它須要Box類:github

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,可使用類型見證指定類型參數,以下所示:segmentfault

BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);

或者,若是省略類型見證,Java編譯器會自動推斷(從方法的參數)類型參數是Integer函數

BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);

類型推斷和泛型類的實例化

只要編譯器能夠從上下文中推斷出類型參數,就能夠用一組空的類型參數(<>)替換調用泛型類的構造函數所需的類型參數,這對尖括號被非正式地稱爲菱形code

例如,請考慮如下變量聲明:對象

Map<String, List<String>> myMap = new HashMap<String, List<String>>();

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

Map<String, List<String>> myMap = new HashMap<>();

請注意,要在泛型類實例化期間利用類型推斷,必須使用菱形,在如下示例中,編譯器生成未經檢查的轉換警告,由於HashMap()構造函數引用HashMap原始類型,而不是Map<String, List<String>>類型:get

Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning

類型推斷和泛型與非泛型類的泛型構造函數

請注意,構造函數在泛型和非泛型類中均可以是泛型的(換句話說,聲明它們本身的形式類型參數),考慮如下示例:

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

考慮如下MyClass類的實例化:

new MyClass<Integer>("")

此語句建立參數化類型MyClass<Integer>的實例,該語句顯式指定泛型類MyClass<X>的形式類型參數X的類型Integer,請注意,此泛型類的構造函數包含形式類型參數T,編譯器爲此泛型類的構造函數的形式類型參數T推斷類型String(由於此構造函數的實際參數是String對象)。

Java SE 7以前版本的編譯器可以推斷泛型構造函數的實際類型參數,相似於泛型方法,可是,若是使用菱形(<>),Java SE 7及更高版本中的編譯器能夠推斷出要實例化的泛型類的實際類型參數,考慮如下示例:

MyClass<Integer> myObject = new MyClass<>("");

在此示例中,編譯器爲泛型類MyClass<X>的形式類型參數X推斷類型Integer,它推斷出此泛型類的構造函數的形式類型參數T的類型String

值得注意的是,推理算法僅使用調用參數、目標類型以及可能明顯的預期返回類型來推斷類型,推理算法不使用程序後面的結果。

目標類型

Java編譯器利用目標類型來推斷泛型方法調用的類型參數,表達式的目標類型是Java編譯器所指望的數據類型,具體取決於表達式的顯示位置,考慮方法Collections.emptyList,聲明以下:

static <T> List<T> emptyList();

考慮如下賦值語句:

List<String> listOne = Collections.emptyList();

此語句指望List<String>的實例,此數據類型是目標類型,由於方法emptyList返回List<T>類型的值,因此編譯器推斷類型參數T必須是值String,這適用於Java SE 7和8,或者,你可使用類型見證並指定T的值,以下所示:

List<String> listOne = Collections.<String>emptyList();

可是,在這種狀況下,這不是必需的,不過,在其餘狀況下這是必要的,考慮如下方法:

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

假設你要使用空列表調用方法processStringList,在Java SE 7中,如下語句不編譯:

processStringList(Collections.emptyList());

Java SE 7編譯器生成相似於如下內容的錯誤消息:

List<Object> cannot be converted to List<String>

編譯器須要類型參數T的值,所以它以值Object開始,所以,Collections.emptyList的調用返回List<Object>類型的值,該值與方法processStringList不兼容,所以,在Java SE 7中,你必須指定類型參數值的值,以下所示:

processStringList(Collections.<String>emptyList());

Java SE 8中再也不須要這樣作,什麼是目標類型的概念已經擴展爲包括方法參數,例如方法processStringList的參數,在這種狀況下,processStringList須要一個List<String>類型的參數,方法Collections.emptyList返回List<T>的值,所以使用List<String>的目標類型,編譯器推斷類型參數T的值爲String,所以,在Java SE 8中,如下語句編譯:

processStringList(Collections.emptyList());

有關詳細信息,請參閱Lambda表達式中的目標類型


上一篇:泛型、繼承和子類型

下一篇:泛型通配符

相關文章
相關標籤/搜索