1. 程式人生 > >java遺珠之泛型型別推斷

java遺珠之泛型型別推斷

型別推斷

型別推斷是java編譯器的一種能力,通過檢視方法呼叫和相應的宣告來決定什麼樣的型別引數或者引數是更為合理的呼叫。

推斷演算法先確定引數的型別,分配結果或者返回的型別,最終推斷演算法查詢適合所有引數最適合的型別。

為了說明這點,來看下面的例子:

public class Util {

    static <T> T pick(T a1, T a2) {
        return a2;
    }

    public static void main(String[] args) {
        Serializable s = pick("d", new ArrayList
<String>()); } }

最終符合所有引數的型別是Serializable

泛型方法裡的型別推斷

泛型方法中的型別推斷可以讓你像呼叫普通方法一樣來呼叫泛型方法,而不用指定泛型方法的型別。

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,編譯器會推斷方法呼叫時候的引數,因此在很多情況下你不需要指定他們。在這裡例子中,你可以明確指定呼叫泛型方法addBox的型別引數:

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

或者,如果你忽略它,編譯器將從方法的傳入引數推斷出型別引數是Integer

泛型類例項化的型別推斷

編譯器能從上下文推斷出型別引數,那麼就可以使用<>

來代替泛型類構造方法的型別引數。

比如:

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

可以用<>來代替構造器的引數化型別。

要注意的是如果想使用型別推斷的特性,你必須加上<>,如果省略就會變成原始型別,而得到unchecked的警告

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

泛型或非泛型類泛型構造方法的推斷

要注意的是不管泛型類還是非泛型類的構造方法都可以是泛型構造方法,看下面的例子

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

這個類的例項化是這樣的:

new MyClass<Integer>("")

這條語句建立了個型別為MyClass<Integer>的例項,語句明確的指定泛型類MyClass<X>中形式型別引數X對應的型別引數為Integer。注意這個類的構造方法也有一個形式型別引數T,因為構造器的實際型別引數為String,編譯器推斷這個泛型類 構造器的形式型別引數為String

編譯器從1.7開始可以像推斷泛型方法一樣可以推斷泛型構造器的實際型別引數,1.7開始編譯器也可以推斷泛型初始化時的實際型別引數,因此就有了以下程式碼

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

目標型別

java編譯器還可以通過目標型別來泛型方法呼叫時的型別引數,表示式的目標型別是編譯器期望的資料型別,具體取決於表示式出現的位置。比如方法Collections.emptyList。在原始碼是這樣定義的

    public static final <T> List<T> emptyList() {
        return (List<T>) EMPTY_LIST;
    }

再看下面的語句

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

這個語句期望返回一個List<String>例項,資料型別就是目標型別。因為emptyList方法返回List<T>型別,編譯器推斷T的型別引數必須是String

在看下面這個普通方法

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

方法引數期望是List<String>,那麼編譯器就會推斷 泛型方法emptyList放在中的型別引數必須是String

processStringList(Collections.emptyList());