多態(tài)是一種面向?qū)ο笏枷氲姆夯瘷C(jī)制。你可以將方法的參數(shù)類(lèi)型設(shè)為基類(lèi),這樣的方法就可以接受任何派生類(lèi)作為參數(shù),包括暫時(shí)還不存在的類(lèi)。這樣的方法更通用,應(yīng)用范圍更廣。在類(lèi)內(nèi)部也是如此,在任何使用特定類(lèi)型的地方,基類(lèi)意味著更大的靈活性。除了 final 類(lèi)(或只提供私有構(gòu)造函數(shù)的類(lèi))任何類(lèi)型都可被擴(kuò)展,所以大部分時(shí)候這種靈活性是自帶的。
拘泥于單一的繼承體系太過(guò)局限,因?yàn)橹挥欣^承體系中的對(duì)象才能適用基類(lèi)作為參數(shù)的方法中。如果方法以接口而不是類(lèi)作為參數(shù),限制就寬松多了,只要實(shí)現(xiàn)了接口就可以。這給予調(diào)用方一種選項(xiàng),通過(guò)調(diào)整現(xiàn)有的類(lèi)來(lái)實(shí)現(xiàn)接口,滿(mǎn)足方法參數(shù)要求。接口可以突破繼承體系的限制。
即便是接口也還是有諸多限制。一旦指定了接口,它就要求你的代碼必須使用特定的接口。而我們希望編寫(xiě)更通用的代碼,能夠適用 “非特定的類(lèi)型”,而不是一個(gè)具體的接口或類(lèi)。
這就是泛型的概念,是 Java 5 的重大變化之一。泛型實(shí)現(xiàn)了參數(shù)化類(lèi)型,這樣你編寫(xiě)的組件(通常是集合)可以適用于多種類(lèi)型。“泛型” 這個(gè)術(shù)語(yǔ)的含義是 “適用于很多類(lèi)型”。編程語(yǔ)言中泛型出現(xiàn)的初衷是通過(guò)解耦類(lèi)或方法與所使用的類(lèi)型之間的約束,使得類(lèi)或方法具備最寬泛的表達(dá)力。
促成泛型出現(xiàn)的最主要的動(dòng)機(jī)之一是為了創(chuàng)建集合類(lèi),集合用于存放要使用到的對(duì)象。數(shù)組也是如此,不過(guò)集合比數(shù)組更加靈活,功能更豐富。幾乎所有程序在運(yùn)行過(guò)程中都會(huì)涉及到一組對(duì)象,因此集合是可復(fù)用性最高的類(lèi)庫(kù)之一。
我們希望先指定一個(gè)類(lèi)型占位符,稍后再?zèng)Q定具體使用什么類(lèi)型。要達(dá)到這個(gè)目的,需要使用類(lèi)型參數(shù),用尖括號(hào)括住,放在類(lèi)名后面。然后在使用這個(gè)類(lèi)時(shí),再用實(shí)際的類(lèi)型替換此類(lèi)型參數(shù)。在下面的例子中,T 就是類(lèi)型參數(shù):
public class GenericHolder{private T a;
public GenericHolder() {}
public T getA() {return a;
}
public void setA(T a) {this.a = a;
}
public static void main(String[] args) {GenericHoldergenericHolder = new GenericHolder<>();
genericHolder.setA("aaaa");
System.out.println(genericHolder.getA());
}
}
創(chuàng)建 GenericHolder 對(duì)象時(shí),必須指明要持有的對(duì)象的類(lèi)型,將其置于尖括號(hào)內(nèi),就像 main() 中那樣使用。然后,你就只能在 GenericHolder 中存儲(chǔ)該類(lèi)型(或其子類(lèi),因?yàn)槎鄳B(tài)與泛型不沖突)的對(duì)象了。當(dāng)你調(diào)用 get() 取值時(shí),直接就是正確的類(lèi)型。這就是 Java 泛型的核心概念:你只需告訴編譯器要使用什么類(lèi)型,剩下的細(xì)節(jié)交給它來(lái)處理。
1.一個(gè)元組類(lèi)庫(kù)有時(shí)一個(gè)方法需要能返回多個(gè)對(duì)象。而 return 語(yǔ)句只能返回單個(gè)對(duì)象,解決方法就是創(chuàng)建一個(gè)對(duì)象,用它打包想要返回的多個(gè)對(duì)象。當(dāng)然,可以在每次需要的時(shí)候,專(zhuān)門(mén)創(chuàng)建一個(gè)類(lèi)來(lái)完成這樣的工作。但是有了泛型,我們就可以一勞永逸。同時(shí),還獲得了編譯時(shí)的類(lèi)型安全。
這個(gè)概念稱(chēng)為元組,它是將一組對(duì)象直接打包存儲(chǔ)于單一對(duì)象中。可以從該對(duì)象讀取其中的元素,但不允許向其中存儲(chǔ)新對(duì)象(這個(gè)概念也稱(chēng)為 數(shù)據(jù)傳輸對(duì)象或 信使)。
通常,元組可以具有任意長(zhǎng)度,元組中的對(duì)象可以是不同類(lèi)型的。不過(guò),我們希望能夠?yàn)槊總€(gè)對(duì)象指明類(lèi)型,并且從元組中讀取出來(lái)時(shí),能夠得到正確的類(lèi)型。要處理不同長(zhǎng)度的問(wèn)題,我們需要?jiǎng)?chuàng)建多個(gè)不同的元組。下面是一個(gè)可以存儲(chǔ)兩個(gè)對(duì)象的元組:
public class Tuple2{public final A a1;
public final B a2;
public Tuple2(A a, B b) {a1 = a;
a2 = b;
}
public String rep() {return a1 + ", " + a2;
}
@Override
public String toString() {return "(" + rep() + ")";
}
}
public class TupleTest {static Tuple2f() {return new Tuple2<>("hi", 47);
}
public static void main(String[] args) {Tuple2ttsi = f();
System.out.println(ttsi);
}
}
構(gòu)造函數(shù)傳入要存儲(chǔ)的對(duì)象。這個(gè)元組隱式地保持了其中元素的次序。
初次閱讀上面的代碼時(shí),你可能認(rèn)為這違反了 Java 編程的封裝原則。a1 和 a2 應(yīng)該聲明為 private,然后提供 getFirst() 和 getSecond() 取值方法才對(duì)呀?考慮下這樣做能提供的 “安全性” 是什么:元組的使用程序可以讀取 a1 和 a2 然后對(duì)它們執(zhí)行任何操作,但無(wú)法對(duì) a1 和 a2 重新賦值。例子中的 final 可以實(shí)現(xiàn)同樣的效果,并且更為簡(jiǎn)潔明了。
2.一個(gè)堆棧類(lèi)用鏈?zhǔn)浇Y(jié)構(gòu)實(shí)現(xiàn)的堆棧:
public class LinkedStack{private static class Node{U item;
Nodenext;
Node() {item = null;
next = null;
}
Node(U item, Nodenext) {this.item = item;
this.next = next;
}
boolean end() {return item == null && next == null;
}
}
// 棧頂
private Nodetop = new Node<>();
public void push(T item) {top = new Node<>(item, top);
}
public T pop() {T result = top.item;
if (!top.end()) {top = top.next;
}
return result;
}
public static void main(String[] args) {LinkedStacklss = new LinkedStack<>();
for (String s : "Phasers on stun!".split(" ")) {lss.push(s);
}
String s;
while ((s = lss.pop()) != null) {System.out.println(s);
}
}
}
內(nèi)部類(lèi) Node 也是一個(gè)泛型,它擁有自己的類(lèi)型參數(shù)。這個(gè)例子使用了一個(gè) 末端標(biāo)識(shí) (end sentinel) 來(lái)判斷棧何時(shí)為空。這個(gè)末端標(biāo)識(shí)是在構(gòu)造 LinkedStack 時(shí)創(chuàng)建的。然后,每次調(diào)用 push() 就會(huì)創(chuàng)建一個(gè) Node 對(duì)象,并將其鏈接到前一個(gè) Node 對(duì)象。當(dāng)你調(diào)用 pop() 方法時(shí),總是返回 top.item,然后丟棄當(dāng)前 top 所指向的 Node,并將 top 指向下一個(gè) Node,除非到達(dá)末端標(biāo)識(shí),這時(shí)就不能再移動(dòng) top 了。如果已經(jīng)到達(dá)末端,程序還繼續(xù)調(diào)用 pop() 方法,它只能得到 null,說(shuō)明棧已經(jīng)空了。
泛型也可以應(yīng)用于接口。例如 生成器,這是一種專(zhuān)門(mén)負(fù)責(zé)創(chuàng)建對(duì)象的類(lèi)。實(shí)際上,這是 工廠(chǎng)方法設(shè)計(jì)模式的一種應(yīng)用。不過(guò),當(dāng)使用生成器創(chuàng)建新的對(duì)象時(shí),它不需要任何參數(shù),而工廠(chǎng)方法一般需要參數(shù)。生成器無(wú)需額外的信息就知道如何創(chuàng)建新對(duì)象。
一般而言,一個(gè)生成器只定義一個(gè)方法,用于創(chuàng)建對(duì)象。例如 java.util.function 類(lèi)庫(kù)中的 Supplier 就是一個(gè)生成器,調(diào)用其 get() 獲取對(duì)象。get() 是泛型方法,返回值為類(lèi)型參數(shù) T。
public class CoffeeSupplier implements Supplier, Iterable{private Class>[] types = {Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class};
private static Random rand = new Random(47);
public CoffeeSupplier() {}
private int size = 0;
public CoffeeSupplier(int sz) {size = sz;
}
@Override
public Coffee get() {try {return (Coffee) types[rand.nextInt(types.length)].newInstance();
} catch (InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);
}
}
@Override
public Iteratoriterator() {return new CoffeeIterator();
}
class CoffeeIterator implements Iterator{int count = size;
@Override
public boolean hasNext() {return count >0;
}
@Override
public Coffee next() {count--;
return CoffeeSupplier.this.get();
}
@Override
public void remove() {throw new UnsupportedOperationException();
}
}
public static void main(String[] args) {Stream.generate(new CoffeeSupplier()).limit(5).forEach(System.out::println);
for (Coffee c : new CoffeeSupplier(5)) {System.out.println(c);
}
}
}
參數(shù)化的 Supplier 接口確保 get() 返回值是參數(shù)的類(lèi)型。CoffeeSupplier 同時(shí)還實(shí)現(xiàn)了 Iterable 接口,所以能用于 for-in 語(yǔ)句。不過(guò),它還需要知道何時(shí)終止循環(huán),這正是第二個(gè)構(gòu)造函數(shù)的作用。
到目前為止,我們已經(jīng)研究了參數(shù)化整個(gè)類(lèi)。其實(shí)還可以參數(shù)化類(lèi)中的方法。類(lèi)本身可能是泛型的,也可能不是,不過(guò)這與它的方法是否是泛型的并沒(méi)有什么關(guān)系。
泛型方法獨(dú)立于類(lèi)而改變方法。作為準(zhǔn)則,請(qǐng) “盡可能” 使用泛型方法。通常將單個(gè)方法泛型化要比將整個(gè)類(lèi)泛型化更清晰易懂。
如果方法是 static 的,則無(wú)法訪(fǎng)問(wèn)該類(lèi)的泛型類(lèi)型參數(shù),因此,如果使用了泛型類(lèi)型參數(shù),則它必須是泛型方法。
要定義泛型方法,請(qǐng)將泛型參數(shù)列表放置在返回值之前,如下所示:
public class GenericMethods {publicvoid f(T x) {System.out.println(x.getClass().getName());
}
public static void main(String[] args) {GenericMethods gm = new GenericMethods();
gm.f("");
gm.f(1);
gm.f(1.0);
gm.f(1.0F);
gm.f('c');
gm.f(gm);
}
}
盡管可以同時(shí)對(duì)類(lèi)及其方法進(jìn)行參數(shù)化,但這里未將 GenericMethods 類(lèi)參數(shù)化。只有方法 f() 具有類(lèi)型參數(shù),該參數(shù)由方法返回類(lèi)型之前的參數(shù)列表指示。
對(duì)于泛型類(lèi),必須在實(shí)例化該類(lèi)時(shí)指定類(lèi)型參數(shù)。使用泛型方法時(shí),通常不需要指定參數(shù)類(lèi)型,因?yàn)榫幾g器會(huì)找出這些類(lèi)型。這稱(chēng)為 類(lèi)型參數(shù)推斷
。因此,對(duì) f() 的調(diào)用看起來(lái)像普通的方法調(diào)用,并且 f() 看起來(lái)像被重載了無(wú)數(shù)次一樣。它甚至?xí)邮蹽enericMethods 類(lèi)型的參數(shù)。
泛型方法和變長(zhǎng)參數(shù)列表可以很好地共存:
public class GenericVarargs {@SafeVarargs
public staticListmakeList(T... args) {Listresult = new ArrayList<>();
for (T item : args) {result.add(item);
}
return result;
}
public static void main(String[] args) {Listls = makeList("A");
System.out.println(ls);
ls = makeList("A", "B", "C");
System.out.println(ls);
ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
System.out.println(ls);
}
}
此處顯示的 makeList() 方法產(chǎn)生的功能與標(biāo)準(zhǔn)庫(kù)的 java.util.Arrays.asList() 方法相同。
@SafeVarargs 注解保證我們不會(huì)對(duì)變長(zhǎng)參數(shù)列表進(jìn)行任何修改,這是正確的,因?yàn)槲覀冎粡闹凶x取。如果沒(méi)有此注解,編譯器將無(wú)法知道這些并會(huì)發(fā)出警告。
2.一個(gè)泛型的 Supplier這是一個(gè)為任意具有無(wú)參構(gòu)造方法的類(lèi)生成 Supplier 的類(lèi)。為了減少鍵入,它還包括一個(gè)用于生成 BasicSupplier 的泛型方法:
public class BasicSupplierimplements Supplier{private Classtype;
public BasicSupplier(Classtype) {this.type = type;
}
@Override
public T get() {try {return type.newInstance();
} catch (InstantiationException |
IllegalAccessException e) {throw new RuntimeException(e);
}
}
public staticSuppliercreate(Classtype) {return new BasicSupplier<>(type);
}
}
此類(lèi)提供了產(chǎn)生以下對(duì)象的基本實(shí)現(xiàn):
static Tuple2f() {return new Tuple2<>("hi", 47);
}
4.一個(gè) Set 工具對(duì)于泛型方法的另一個(gè)示例,請(qǐng)考慮由 Set 表示的數(shù)學(xué)關(guān)系。這些被方便地定義為可用于所有不同類(lèi)型的泛型方法:
public class Sets {public staticSetunion(Seta, Setb) {Setresult = new HashSet<>(a);
result.addAll(b);
return result;
}
public staticSetintersection(Seta, Setb) {Setresult = new HashSet<>(a);
result.retainAll(b);
return result;
}
public staticSetdifference(Setsuperset, Setsubset) {Setresult = new HashSet<>(superset);
result.removeAll(subset);
return result;
}
public staticSetcomplement(Seta, Setb) {return difference(union(a, b), intersection(a, b));
}
}
前三個(gè)方法通過(guò)將第一個(gè)參數(shù)的引用復(fù)制到新的 HashSet 對(duì)象中來(lái)復(fù)制第一個(gè)參數(shù),因此不會(huì)直接修改參數(shù)集合。因此,返回值是一個(gè)新的 Set 對(duì)象。
這四種方法代表數(shù)學(xué)集合操作:union() 返回一個(gè)包含兩個(gè)參數(shù)并集的 Set ,intersection() 返回一個(gè)包含兩個(gè)參數(shù)集合交集的 Set ,difference() 從 superset中減去 subset 的元素,而 complement() 返回所有不在交集中的元素的 Set。
四、構(gòu)建復(fù)雜模型泛型的一個(gè)重要好處是能夠簡(jiǎn)單安全地創(chuàng)建復(fù)雜模型。例如,我們可以輕松地創(chuàng)建一個(gè)元組列表:
public class TupleListextends ArrayList>{public static void main(String[] args) {TupleListtl = new TupleList<>();
tl.add(TupleTest.f());
tl.add(TupleTest.f());
tl.forEach(System.out::println);
}
}
這將產(chǎn)生一個(gè)功能強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),而無(wú)需太多代碼。
public class LostInformation {public static void main(String[] args) {Listlist = new ArrayList<>();
Mapmap = new HashMap<>();
Quarkquark = new Quark<>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
}
static class Frob {}
static class Fnorkle {}
static class Quark{}
}
根據(jù) JDK 文檔,Class.getTypeParameters() “返回一個(gè) TypeVariable 對(duì)象數(shù)組,表示泛型聲明中聲明的類(lèi)型參數(shù)…” 這暗示你可以發(fā)現(xiàn)這些參數(shù)類(lèi)型。但是正如上例中輸出所示,你只能看到用作參數(shù)占位符的標(biāo)識(shí)符,這并非有用的信息。
殘酷的現(xiàn)實(shí)是:在泛型代碼內(nèi)部,無(wú)法獲取任何有關(guān)泛型參數(shù)類(lèi)型的信息。因此,你可以知道如類(lèi)型參數(shù)標(biāo)識(shí)符和泛型邊界這些信息,但無(wú)法得知實(shí)際的類(lèi)型參數(shù)從而用來(lái)創(chuàng)建特定的實(shí)例。在使用 Java 泛型工作時(shí),它是必須處理的最基本的問(wèn)題。
Java 泛型是使用擦除實(shí)現(xiàn)的。這意味著當(dāng)你在使用泛型時(shí),任何具體的類(lèi)型信息都被擦除了,你唯一知道的就是你在使用一個(gè)對(duì)象。因此,List
如果 Java 1.0 就含有泛型的話(huà),那么這個(gè)特性就不會(huì)使用擦除來(lái)實(shí)現(xiàn)——它會(huì)使用具體化,保持參數(shù)類(lèi)型為第一類(lèi)實(shí)體,因此你就能在類(lèi)型參數(shù)上執(zhí)行基于類(lèi)型的語(yǔ)言操作和反射操作。泛型在 Java 中仍然是有用的,只是不如它們本來(lái)設(shè)想的那么有用,而原因就是擦除。
在基于擦除的實(shí)現(xiàn)中,泛型類(lèi)型被當(dāng)作第二類(lèi)類(lèi)型處理,即不能在某些重要的上下文使用泛型類(lèi)型。泛型類(lèi)型只有在靜態(tài)類(lèi)型檢測(cè)期間才出現(xiàn),在此之后,程序中的所有泛型類(lèi)型都將被擦除,替換為它們的非泛型上界。例如,List 這樣的類(lèi)型注解會(huì)被擦除為 List,普通的類(lèi)型變量在未指定邊界的情況下會(huì)被擦除為 Object。
擦除的核心動(dòng)機(jī)是你可以在泛化的客戶(hù)端上使用非泛型的類(lèi)庫(kù),反之亦然。這經(jīng)常被稱(chēng)為 “遷移兼容性”。在理想情況下,所有事物將在指定的某天被泛化。在現(xiàn)實(shí)中,即使程序員只編寫(xiě)泛型代碼,他們也必須處理 Java 5 之前編寫(xiě)的非泛型類(lèi)庫(kù)。這些類(lèi)庫(kù)的作者可能從沒(méi)想過(guò)要泛化他們的代碼,或許他們可能剛剛開(kāi)始接觸泛型。
因此 Java 泛型不僅必須支持向后兼容性——現(xiàn)有的代碼和類(lèi)文件仍然合法,繼續(xù)保持之前的含義——而且還必須支持遷移兼容性,使得類(lèi)庫(kù)能按照它們自己的步調(diào)變?yōu)榉盒?,?dāng)某個(gè)類(lèi)庫(kù)變?yōu)榉盒蜁r(shí),不會(huì)破壞依賴(lài)于它的代碼和應(yīng)用。在確定了這個(gè)目標(biāo)后,Java 設(shè)計(jì)者們和從事此問(wèn)題相關(guān)工作的各個(gè)團(tuán)隊(duì)決策認(rèn)為擦除是唯一可行的解決方案。擦除使得這種向泛型的遷移成為可能,允許非泛型的代碼和泛型代碼共存。
2.擦除的問(wèn)題因此,擦除主要的正當(dāng)理由是從非泛化代碼到泛化代碼的轉(zhuǎn)變過(guò)程,以及在不破壞現(xiàn)有類(lèi)庫(kù)的情況下將泛型融入到語(yǔ)言中。擦除允許你繼續(xù)使用現(xiàn)有的非泛型客戶(hù)端代碼,直至客戶(hù)端準(zhǔn)備好用泛型重寫(xiě)這些代碼。這是一個(gè)崇高的動(dòng)機(jī),因?yàn)樗粫?huì)驟然破壞所有現(xiàn)有的代碼。
擦除的代價(jià)是顯著的。泛型不能用于顯式地引用運(yùn)行時(shí)類(lèi)型的操作中,例如轉(zhuǎn)型、instanceof 操作和 new 表達(dá)式。因?yàn)樗嘘P(guān)于參數(shù)的類(lèi)型信息都丟失了,當(dāng)你在編寫(xiě)泛型代碼時(shí),必須時(shí)刻提醒自己,你只是看起來(lái)?yè)碛杏嘘P(guān)參數(shù)的類(lèi)型信息而已??紤]如下的代碼段:
class Foo{T var;
}
Foof = new Foo<>();
class Foo 中的代碼應(yīng)該知道現(xiàn)在工作于 Cat 之上。泛型語(yǔ)法也在強(qiáng)烈暗示整個(gè)類(lèi)中所有 T 出現(xiàn)的地方都被替換,當(dāng)你在編寫(xiě)這個(gè)類(lèi)的代碼時(shí),必須提醒自己:“不,這只是一個(gè) Object“。
另外,擦除和遷移兼容性意味著,使用泛型并不是強(qiáng)制的:
public class GenericBase{private T element;
public void set(T arg) {element = arg;
}
public T get() {return element;
}
}
class Derived1extends GenericBase{}
class Derived2 extends GenericBase {}
3.邊界處的動(dòng)作因?yàn)椴脸?,發(fā)現(xiàn)泛型最令人困惑的方面是可以表示沒(méi)有任何意義的事物。例如:
public class ArrayMaker{private Classkind;
public ArrayMaker(Classkind) {this.kind = kind;
}
@SuppressWarnings("unchecked")
T[] create(int size) {return (T[]) Array.newInstance(kind, size);
}
public static void main(String[] args) {ArrayMakerstringMaker = new ArrayMaker<>(String.class);
String[] stringArray = stringMaker.create(9);
System.out.println(Arrays.toString(stringArray));
}
}
即使 kind 被存儲(chǔ)為 Class
如果我們創(chuàng)建一個(gè)集合而不是數(shù)組,情況就不同了:
public class ListMaker{Listcreate() {return new ArrayList<>();
}
public static void main(String[] args) {ListMakerstringMaker = new ListMaker<>();
ListstringList = stringMaker.create();
}
}
編譯器不會(huì)給出任何警告,盡管我們知道(從擦除中)在 create() 內(nèi)部的 newArrayList<>() 中的 被移除了——在運(yùn)行時(shí),類(lèi)內(nèi)部沒(méi)有任何
本例中這么做真的毫無(wú)意義嗎?如果在創(chuàng)建 List 的同時(shí)向其中放入一些對(duì)象呢,像這樣:
public class FilledListextends ArrayList{public FilledList(T t, int size) {for (int i = 0; i< size; i++) {this.add(t);
}
}
public static void main(String[] args) {Listlist = new FilledList<>("Hello", 4);
System.out.println(list);
}
}
即使編譯器無(wú)法得知 add() 中的 T 的任何信息,但它仍可以在編譯期確保你放入FilledList 中的對(duì)象是 T 類(lèi)型。因此,即使擦除移除了方法或類(lèi)中的實(shí)際類(lèi)型的信息,編譯器仍可以確保方法或類(lèi)中使用的類(lèi)型的內(nèi)部一致性。
因?yàn)椴脸瞥朔椒w中的類(lèi)型信息,所以在運(yùn)行時(shí)的問(wèn)題就是邊界:即對(duì)象進(jìn)入和離開(kāi)方法的地點(diǎn)。這些正是編譯器在編譯期執(zhí)行類(lèi)型檢查并插入轉(zhuǎn)型代碼的地點(diǎn)。
因?yàn)椴脸?,我們將失去?zhí)行泛型代碼中某些操作的能力。無(wú)法在運(yùn)行時(shí)知道確切類(lèi)型:
public class Erased{private final int SIZE = 100;
public void f(Object arg) {// error: illegal generic type for instanceof
if (arg instanceof T) {}
// error: unexpected type
T var = new T();
// error: generic array creation
T[] array = new T[SIZE];
// warning: [unchecked] unchecked cast
T[] array = (T[]) new Object[SIZE];
}
}
有時(shí),我們可以對(duì)這些問(wèn)題進(jìn)行編程,但是有時(shí)必須通過(guò)引入類(lèi)型標(biāo)簽來(lái)補(bǔ)償擦除。這意味著為所需的類(lèi)型顯式傳遞一個(gè) Class 對(duì)象,以在類(lèi)型表達(dá)式中使用它。
public class ClassTypeCapture{Classkind;
public ClassTypeCapture(Classkind) {this.kind = kind;
}
public boolean f(Object arg) {return kind.isInstance(arg);
}
public static void main(String[] args) {ClassTypeCapturectt1 = new ClassTypeCapture<>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapturectt2 = new ClassTypeCapture<>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
}
編譯器來(lái)保證類(lèi)型標(biāo)簽與泛型參數(shù)相匹配。
試圖在類(lèi)中 new T() 是行不通的,部分原因是由于擦除,部分原因是編譯器無(wú)法驗(yàn)證 T 是否具有默認(rèn)(無(wú)參)構(gòu)造函數(shù)。 Java 中的解決方案是傳入一個(gè)工廠(chǎng)對(duì)象,并使用該對(duì)象創(chuàng)建新實(shí)例。方便的工廠(chǎng)對(duì)象只是 Class 對(duì)象,因此,如果使用類(lèi)型標(biāo)記,則可以使用 newInstance() 創(chuàng)建該類(lèi)型的新對(duì)象:
public class ClassAsFactoryimplements Supplier{Classkind;
ClassAsFactory(Classkind) {this.kind = kind;
}
@Override
public T get() {try {return kind.newInstance();
} catch (InstantiationException |
IllegalAccessException e) {throw new RuntimeException(e);
}
}
public static void main(String[] args) {ClassAsFactoryfii = new ClassAsFactory<>(Building.class);
System.out.println(fii.get());
// ClassAsFactoryfi = new ClassAsFactory<>(Integer.class);
// System.out.println(fi.get());
}
}
對(duì)于 ClassAsFactory
public class IntegerFactory implements Supplier{private int i = 0;
@Override
public Integer get() {return ++i;
}
}
public class Widget {private int id;
Widget(int n) {id = n;
}
@Override
public String toString() {return "Widget " + id;
}
public static class Factory implements Supplier{private int i = 0;
@Override
public Widget get() {return new Widget(++i);
}
}
}
public class Fudge {private static int count = 1;
private int n = count++;
@Override
public String toString() {return "Fudge " + n;
}
}
public class Foo2{private Listx = new ArrayList<>();
Foo2(Supplierfactory) {Suppliers.fill(x, factory, 5);
}
@Override
public String toString() {return x.toString();
}
}
public class Suppliers {// Create a collection and fill it:
public static>C
create(Supplierfactory, Suppliergen, int n) {return Stream.generate(gen)
.limit(n)
.collect(factory, C::add, C::addAll);
}
// Fill an existing collection:
public static>C fill(C coll, Suppliergen, int n) {Stream.generate(gen)
.limit(n)
.forEach(coll::add);
return coll;
}
// Use an unbound method reference to
// produce a more general method:
public staticH fill(H holder, BiConsumeradder, Suppliergen, int n) {Stream.generate(gen)
.limit(n)
.forEach(a ->adder.accept(holder, a));
return holder;
}
}
public class FactoryConstraint {public static void main(String[] args) {System.out.println(new Foo2<>(new IntegerFactory()));
System.out.println(new Foo2<>(new Widget.Factory()));
System.out.println(new Foo2<>(Fudge::new));
}
}
IntegerFactory 本身就是通過(guò)實(shí)現(xiàn) Supplier
另一種方法是模板方法設(shè)計(jì)模式。在以下示例中,create() 是模板方法,在子類(lèi)中被重寫(xiě)以生成該類(lèi)型的對(duì)象:
public abstract class GenericWithCreate{final T element;
GenericWithCreate() {element = create();
}
abstract T create();
}
public class X {}
public class XCreator extends GenericWithCreate{@Override
X create() {return new X();
}
void f() {System.out.println(element.getClass().getSimpleName());
}
}
public class CreatorGeneric {public static void main(String[] args) {XCreator xc = new XCreator();
xc.f();
}
}
GenericWithCreate 包含 element 字段,并通過(guò)無(wú)參構(gòu)造函數(shù)強(qiáng)制其初始化,該構(gòu)造函數(shù)又調(diào)用抽象的 create() 方法。這種創(chuàng)建方式可以在子類(lèi)中定義,同時(shí)建立 T 的類(lèi)型。
邊界允許我們對(duì)泛型使用的參數(shù)類(lèi)型施加約束。盡管這可以強(qiáng)制執(zhí)行有關(guān)應(yīng)用了泛型類(lèi)型的規(guī)則,但潛在的更重要的效果是我們可以在綁定的類(lèi)型中調(diào)用方法
。
由于擦除會(huì)刪除類(lèi)型信息,因此唯一可用于無(wú)限制泛型參數(shù)的方法是那些 Object可用的方法。但是,如果將該參數(shù)限制為某類(lèi)型的子集,則可以調(diào)用該子集中的方法。為了應(yīng)用約束,Java 泛型使用了 extends 關(guān)鍵字。
重要的是要理解,當(dāng)用于限定泛型類(lèi)型時(shí),extends 的含義與通常的意義截然不同。此示例展示邊界的基礎(chǔ)應(yīng)用:
public class Coord {public int x, y, z;
public Coord(int x, int y, int z) {this.x = x;
this.y = y;
this.z = z;
}
}
public class Bounded extends Coord{public Bounded(int x, int y, int z) {super(x, y, z);
}
}
public class Solid{T item;
public Solid(T t) {item = t;
}
int getX() {return item.x;
}
int getY() {return item.y;
}
int getZ() {return item.z;
}
public static void main(String[] args) {Solidsolid = new Solid<>(new Bounded(1, 2, 3));
System.out.println(solid.getX());
}
}
八、通配符你想在兩個(gè)類(lèi)型間建立某種向上轉(zhuǎn)型關(guān)系,通配符可以產(chǎn)生這種關(guān)系。
public class GenericsAndCovariance {public static void main(String[] args) {List extends Fruit>flist = new ArrayList<>();
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null);
Fruit f = flist.get(0);
}
}
flist 的類(lèi)型現(xiàn)在是 List extends Fruit>,你可以讀作 “一個(gè)具有任何從 Fruit繼承的類(lèi)型的列表”。然而,這實(shí)際上并不意味著這個(gè) List 將持有任何類(lèi)型的 Fruit。通配符引用的是明確的類(lèi)型,因此它意味著 “某種 flist 引用沒(méi)有指定的具體類(lèi)型”。因此這個(gè)被賦值的 List 必須持有諸如 Fruit 或 Apple 這樣的指定類(lèi)型,但是為了向上轉(zhuǎn)型為 Fruit,這個(gè)類(lèi)型是什么沒(méi)人在意。
List 必須持有一種具體的 Fruit 或 Fruit 的子類(lèi)型,但是如果你不關(guān)心具體的類(lèi)型是什么,那么你能對(duì)這樣的 List 做什么呢?如果不知道 List 中持有的對(duì)象是什么類(lèi)型,你怎能保證安全地向其中添加對(duì)象呢?就像在 CovariantArrays.java 中向上轉(zhuǎn)型一樣,你不能,除非編譯器而不是運(yùn)行時(shí)系統(tǒng)可以阻止這種操作的發(fā)生。你很快就會(huì)發(fā)現(xiàn)這個(gè)問(wèn)題。
你可能認(rèn)為事情開(kāi)始變得有點(diǎn)走極端了,因?yàn)楝F(xiàn)在你甚至不能向剛剛聲明過(guò)將持有Apple 對(duì)象的 List 中放入一個(gè) Apple 對(duì)象。是的,但編譯器并不知道這一點(diǎn)。List extends Fruit>可能合法地指向一個(gè) List
這里,可以聲明通配符是由某個(gè)特定類(lèi)的任何基類(lèi)來(lái)界定的,方法是指定<?super MyClass>,或者甚至使用類(lèi)型參數(shù):<?super T>(盡管你不能對(duì)泛型參數(shù)給出一個(gè)超類(lèi)型邊界;即不能聲明
public class SuperTypeWildcards {static void writeTo(List super Fruit>apples) {apples.add(new Apple());
apples.add(new Orange());
}
}
2.無(wú)界通配符無(wú)界通配符>看起來(lái)意味著 “任何事物”,因此使用無(wú)界通配符好像等價(jià)于使用原生類(lèi)型。事實(shí)上,編譯器初看起來(lái)是支持這種判斷的:
public class UnboundedWildcards1 {static List list1;
static List>list2;
static void assign1(List list) {list1 = list;
list2 = list;
}
public static void main(String[] args) {assign1(new ArrayList());
}
}
普通的類(lèi)和方法只能使用特定的類(lèi)型:基本數(shù)據(jù)類(lèi)型或類(lèi)類(lèi)型。如果編寫(xiě)的代碼需要應(yīng)用于多種類(lèi)型,這種嚴(yán)苛的限制對(duì)代碼的束縛就會(huì)很大,泛型使我們能編寫(xiě)更通用的代碼。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購(gòu),新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧
本文題目:JavaSE筆記——泛型-創(chuàng)新互聯(lián)
網(wǎng)址分享:http://jinyejixie.com/article42/pejhc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制開(kāi)發(fā)、關(guān)鍵詞優(yōu)化、做網(wǎng)站、網(wǎng)站建設(shè)、域名注冊(cè)、搜索引擎優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容