程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> java基礎-泛型2,java基礎泛型

java基礎-泛型2,java基礎泛型

編輯:JAVA綜合教程

java基礎-泛型2,java基礎泛型


6 類型推測

  java編譯器能夠檢查所有的方法調用和對應的聲明來決定類型的實參,即類型推測,類型的推測算法推測滿足所有參數的最具體類型,如下例所示:

//泛型方法的聲明
static <T> T pick(T a1, T a2) { return a2; }
//調用該方法,根據賦值對象的類型,推測泛型方法的類型參數為Serializable
//String和ArrayList<T>都實現接口Serializable,後者是最具體的類型
Serializable s = pick("d", new ArrayList<String>());

6.1 泛型方法的類型推測

  類型的推測可以使泛型方法的使用語法和普通的方法一樣,不必指定尖括號內的類型,如上述例子。

6.2 泛型類的類型推測

  對於泛型類的使用,java編譯器也可以進行類型的推測,因此調用泛型類時,可以不用指定尖括號內的類型參數,不過尖括號不可省略,之前的總結已經提到,空的尖括號又叫鑽石(中文怪怪的),如下例所示:

//以下用法沒有指定類型參數,尖括號為空
Map<String, List<String>> myMap = new HashMap<>();
//注意,空的簡括號不能省略,如下代碼編譯器會發出警告
Map<String, List<String>> myMap = new HashMap();

   上述代碼中的第二個賦值語句中new HashMap() 實際是用的原始類型。

6.3 非泛型類的泛型構造器的類型參數推測

  無論是泛型還是非泛型的類都可以使用泛型的構造器,如方法一樣。

//類定義
class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}
//以下是實例化以上類的表達式
new MyClass<Integer>("")

  以上代碼中的實例化表達式雖然沒有指定構造器的類型參數,但是可以根據傳入的參數推測其類型參數為String。

  java7以前的版本能夠推測出構造其的參數類型,而java7以後,使用鑽石的語法也推測泛型類的參數類型。

  需要注意的是,類型參數的推測算法只會使用傳入的參數,目的類型或者和明顯的返回類型來推測類型。

6.4 目的類型

  java編譯器充分利用了目的類型來推測泛型方法或者類的類型參數,如下例:

//Collections中的一個方法的聲明如下
static <T> List<T> emptyList();
//現在調用該方法
List<String> listOne = Collections.emptyList();

  以上中的第二個語句中,listOne變量類型為List<string>,就是目的類型,所以需要方法emptyList的返回類型也必須是List<Stirng>,這樣可以推測泛型方法聲明中的T為String,java7和8都可以實現這樣的推測,當然你可以在調用泛型方法時指明方括號中的類型參數。

  值得注意的是java7中方法的參數還不屬於目的類型,而java8則把方法參數加入目的類型,如下例所示:

//如下方法接受的參數為List<String> 
void processStringList(List<String> stringList) {
    // process stringList
}
//Collections中的emptyList方法的簽名如下
static <T> List<T> emptyList();
//java7中,下列調用語句的編譯會報錯,而java8則不存在這樣的問題
processStringList(Collections.emptyList());

 7 通配符

  在泛型的代碼中問號(?)代表通配符,代表未知的類型,通配符可以用在許多場合,可用作參數,字段,返回值的類型,但是通配符不能用作方法調用,泛型實例的創建和父類型的實參。

7.1 上限通配符

  利用上限通配符可以放松對變量的限制。

  上限通配符的聲明方法如下例所示:

public static void process(List<? extends Foo> list) { /* ... */ }

  上述聲明的方法,的泛型參數使用了上限通配符,通配符"?"加extends關鍵詞後跟其上限,此處的extends類似於通常意義上的extends和implements,意思是該方法是針對於Number類型的子類型,包括Integer,Float等的列表。

  通配符<? extends Foo>匹配所有的Foo的子類型和Foo類型自身。

7.2 無限制通配符

  無限制通配符就是簡單的"?",如List<?>就代表未知類型的列表,以下兩種情況適合使用無限制通配符:

  • 聲明一個要用到繼承的Object類中的方法時
  • 當代碼中需要用到不依賴於類型參數的泛型類的方法時, 如List.size或者List.clear,Class<?> 經常被用到,因為Class<T>中的許多方法是不依賴於類型參數T的。

以下例子很好的說明使用Object類中的方法時使用無限制通配符的好處:

//普通的方法聲明
public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}
//使用通配符的泛型作為方法參數,該方法的參數能夠傳入任何類型的列表(List)
public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

   注意:既然定義了列表List<?>的類型的廣泛性,就要承擔廣泛性的造成的後果,在方法聲明中,只能對List<?>類型的變量插入null,因為你無法預知傳入方法的類型變量,而List<Object>作為參數則可以插入任何類型的對象。

7.3 下限通配符

   與上限通配符類似,下限通配符指定了類型參數的下限,未知的類型必須是指定類型的父類型,下限通配符的寫法:<? super A>,此處關鍵詞為super

  注意:不能同時指定上限和下限。

7.4 通配符和子類型

  之前提到過,泛型之間的關系不僅僅是由他們的類型實參決定的,如不能說List<Number>就是List<Integer>的父類,不過使用通配符可以構成如下關系:

  箭頭表示“是其子類型”的關系,如List<Integer>是List<? extends Integer>的子類型,可以這樣理解:List<Integer>是一種List<? extends Integer>。

7.5 通配符的捕獲與輔助方法

   有時候編譯器會推測通配符的類型,如果一個字段的類型被定義為List<?>,當運算一個表達式的時候,編譯器會從代碼中推測該字段為一個特定的類型,這就叫通配符的捕獲。

import java.util.List;
public class WildcardError {
    void foo(List<?> i) {
        i.set(0, i.get(0));
    }
}

 

  上述代碼會編譯出錯,foo方法調用List.set(int,E),編譯器首先將set方法內作為參數的i視為Object類型,無法判斷將要插入的對象類型是否和目標列表類型是否一致,所以編譯不能通過。

  此時可以加入一個輔助方法,使其能能夠順利通過編譯:

public class WildcardFixed {

    void foo(List<?> i) {
        fooHelper(i);
    }
    // 創建輔助方法,調用該方法可以通過類型推測來實現通配符的捕獲
    private <T> void fooHelper(List<T> l) {
        l.set(0, l.get(0));
    }
}

  再來看一下一個例子:

import java.util.List;

public class WildcardErrorBad {

    void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
      Number temp = l1.get(0);
      l1.set(0, l2.get(0)); 
      l2.set(0, temp);       
    }
}

  上述代碼中的方法功能是將兩個列表的首個元素交換,然而無法判斷兩個傳入的實參的類型參數是否兼容,所以,無法編譯通過,此處代碼本質上就是錯誤的,沒有相應的輔助方法。

 7.6 通配符使用原則

  泛型的使用有一點讓人疑惑的就是不知道什麼時候該用上限通配符,什麼時候使用下限通配符,一下是幾點原則:

  為了說明問題,先列出兩種變量1)In變量:作為代碼中的數據來源,比如復制的方法copy(src,dest)中的src參數就是in變量,;2)out變量,在代碼中用來存儲數據作為他用,如copy(src,dest)中的dest參數就是out變量。變量列出之後,說原則:

  • in變量使用上限通配符,使用extends關鍵詞
  • out變量使用下限通配符,使用super關鍵詞
  • 當需要使用的in變量可以通過Object類中的方法訪問時,使用無限制通配符
  • 當代碼中既需要訪問的變量既要當做in變量使用,又要當做out變量使用時,不要使用通配符

  上述原則不試用與方法的返回類型,不建議在返回類型中使用通配符,否則將必須處理通配符的問題。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved