泛型方法
首先需要知道的是,可以在類中包含參數化方法,而這個方法所在的類可以是泛型類,也可以不是泛型類。也就是說,是否擁有泛型方法,與其所在的類是否是泛型沒有關係。泛型方法使得該方法能夠獨立於類而產生變化。一個基本原則是:無論何時,只要你能做到,你就應該儘量使用泛型方法。也就是說,如果使用泛型方法可以取代將整個類泛型化,那麼就應該只使用泛型方法,因爲它可以使事情更清楚明白。另外,對於一個 static 的方法而言,無法訪問泛型類的類型參數,所以,如果 static 方法需要使用泛型能力,就必須使其成爲泛型方法。
要定義泛型方法,只需將泛型參數列表置於返回值之前,比如:public void f(T x){};
1)槓桿利用類型參數推斷
在泛型方法中,類型參數推斷可以簡化一部分工作。例如,編寫一個工具類,它包含各種各樣的static方法,專門用來創建各種常用的容器對象,例如:
public class New {
public static <K,V> Map<K,V> map() {
return new HashMap<K, V>();
}
public static <T> List<T> list() {
return new ArrayList<T>();
}
public static <T> LinkedList<T> lList(){
return new LinkedList<T>();
}
public static <T> Set<T> set(){
return new HashSet<T>();
}
public static <T> Queue<T> queue(){
return new LinkedList<T>();
}
public static void main(String[] args) {
Map<String, List<String>> sls = New.map();
List<String> ls = New.lList();
Set<String> ss = New.set();
Queue<String> qs = New.queue();
}
}
main()方法演示瞭如何使用這個工具類,類型參數推斷避免了重複的泛型參數列表。類型推斷只對複製操作有效,其他時候並不起作用。如果你將一個泛型方法調用的結果作爲參數,傳遞給另一個方法,這時編譯器並不會執行類型推斷。
顯示的類型說明,在泛型方法中,可以顯示地指明類型,不過這種語法很少用。要顯示地指明類型,必須在點操作符之前使用this關鍵字,如果是使用static的方法,必須在點操作符之前加上類名。例如New.<Person,List<Pet>> map();
2)可變參數與泛型方法
泛型方法與可變參數列表能夠很好地共存,例如:
public class GenericVarargs {
public static <T> List<T> makeListT(T... args){
List<T> reList = new ArrayList<T>();
for (T item : args) {
reList.add(item);
}
return reList;
}
public static void main(String[] args) {
List<String> ls = makeListT("A");
System.out.println(ls);
ls = makeListT("A","B","C");
System.out.println(ls);
ls = makeListT("ABCDEFHIJKLMNOPQRSTUVWXYZ".split(""));
System.out.println(ls);
}
}
makeListT()方法展示了與標準類庫中java.util.ArrayList()方法相同的功能。
3)用於Generator的泛型方法
利用生成器,可以很方便地填充一個Collection,而泛型化這種操作時具有實際意義的,例如:
public class Generators {
public static <T> Collection<T> fill(Collection<T> coll,Generator<T> gen,int n) {
for (int i = 0; i < n; i++) {
coll.add(gen.next());
}
return coll;
}
public static void main(String[] args) {
Collection<Coffee> coffees = fill(new ArrayList<Coffee>(), new CoffeeGenerator(), 4);
for (Coffee coffee : coffees) {
System.out.println(coffee);
}
}
}
其中Generator接口、Coffee類以及CoffeeGenerator類在上一篇博客中有,在這個例子中,fill()方法使如何透明地應用於Coffee的容器。
4)一個通用的Generator
下面的程序可以爲任何類構造一個Generator,只要該類具有默認的構造器。爲了減少類型聲明,它提供了一個泛型方法,用以生成BasicGenerator:
public class BasicGenerator<T> implements Generator<T>{
private Class<T> type;
public BasicGenerator(Class<T> type){
this.type = type;
}
@Override
public T next() {
try {
return type.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static <T> Generator<T> create(Class<T> type) {
return new BasicGenerator<T>(type);
}
}
這個類提供了一個基本實現,用以生成某個類的對象。這個類必須具備兩個特點:(1)它必須聲明爲public。(因爲BasicGenerator與要處理的類在不同的包中,所以該類必須聲明爲public,並且不只具有包內訪問權限。)(2)它必須具備默認的構造器(無參的構造器)。要創建這樣的BasicGenerator對象,只需調用create()方法,並傳入想要生成的類型。泛型化的create()方法允許執行BasicGenerator.create(MyType.class),而不必執行麻煩的new BasicGenerator(MyType.class)。
例如,下面是一個具有默認構造器的簡單的類:
public class CountObject {
private static long counter = 0;
private final long id = counter ++;
public long id(){
return id;
}
public String toString(){
return "CountObject" + id;
}
}
CountObject類能夠記錄下它創建了多少個CountObject實例,並通過toString()方法告訴我們其編號。
使用BasicGenerator,你可以很容易地位CountObject創建一個Generator:
public class BasicGeneratorDemo {
public static void main(String[] args) {
Generator<CountObject> generator = BasicGenerator.create(CountObject.class);
for (int i = 0; i < 5; i++) {
System.out.println(generator.next());
}
}
}
output:
CountObject0
CountObject1
CountObject2
CountObject3
CountObject4
可以看到,使用泛型方法創建Generator對象,大大減少了我們要編寫的代碼。java泛型要求傳入Class對象,以便也可以在create()方法中用它進行類型推斷。
5)一個Set的實用工具
作爲泛型方法的另一個示例,通過使用泛型方法,可以很方便地做到使用Set來表達數學中的關係式,而且可以應用於多種類型:
public class Sets {
public static <T> Set<T> union(Set<T> a,Set<T> b){
Set<T> result = new HashSet<T>(a);
result.addAll(b);
return result;
}
public static <T> Set<T> intert(Set<T> a,Set<T> b){
Set<T> result = new HashSet<T>(a);
result.retainAll(b);
return result;
}
public static <T> Set<T> difference(Set<T> superset,Set<T> subset){
Set<T> result = new HashSet<T>(subset);
result.removeAll(subset);
return result;
}
public static <T> Set<T> complement(Set<T> a,Set<T> b){
return difference(union(a, b), intert(a, b));
}
}
在前三個方法中,都將第一個參數Set複製了一份,將Set中的所有引用都存入一個新的HashSet對象中,因此,我們並未直接修改參數中的Set。返回值是一個全新的Set對象。這四個方法表達了一個數學集合的操作,union()返回一個Set,它將兩個參數合併在一起,intert()返回的Set只包含兩個參數共有的部分;difference()方法從superset中移除subset包含的元素;complement()返回的Set包含除了交集之外的所有元素。