
多態(tài)是一種面向?qū)ο笏枷氲姆夯瘷C(jī)制。你可以將方法的參數(shù)類型設(shè)為基類,這樣的方法就可以接受任何派生類作為參數(shù),包括暫時還不存在的類。這樣的方法更通用,應(yīng)用范圍更廣。在類內(nèi)部也是如此,在任何使用特定類型的地方,基類意味著更大的靈活性。除了 final 類(或只提供私有構(gòu)造函數(shù)的類)任何類型都可被擴(kuò)展,所以大部分時候這種靈活性是自帶的。
拘泥于單一的繼承體系太過局限,因為只有繼承體系中的對象才能適用基類作為參數(shù)的方法中。如果方法以接口而不是類作為參數(shù),限制就寬松多了,只要實現(xiàn)了接口就可以。這給予調(diào)用方一種選項,通過調(diào)整現(xiàn)有的類來實現(xiàn)接口,滿足方法參數(shù)要求。接口可以突破繼承體系的限制。
即便是接口也還是有諸多限制。一旦指定了接口,它就要求你的代碼必須使用特定的接口。而我們希望編寫更通用的代碼,能夠適用 “非特定的類型”,而不是一個具體的接口或類。
這就是泛型的概念,是 Java 5 的重大變化之一。泛型實現(xiàn)了參數(shù)化類型,這樣你編寫的組件(通常是集合)可以適用于多種類型。“泛型” 這個術(shù)語的含義是 “適用于很多類型”。編程語言中泛型出現(xiàn)的初衷是通過解耦類或方法與所使用的類型之間的約束,使得類或方法具備最寬泛的表達(dá)力。
促成泛型出現(xiàn)的最主要的動機(jī)之一是為了創(chuàng)建集合類,集合用于存放要使用到的對象。數(shù)組也是如此,不過集合比數(shù)組更加靈活,功能更豐富。幾乎所有程序在運(yùn)行過程中都會涉及到一組對象,因此集合是可復(fù)用性最高的類庫之一。
我們希望先指定一個類型占位符,稍后再決定具體使用什么類型。要達(dá)到這個目的,需要使用類型參數(shù),用尖括號括住,放在類名后面。然后在使用這個類時,再用實際的類型替換此類型參數(shù)。在下面的例子中,T 就是類型參數(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 對象時,必須指明要持有的對象的類型,將其置于尖括號內(nèi),就像 main() 中那樣使用。然后,你就只能在 GenericHolder 中存儲該類型(或其子類,因為多態(tài)與泛型不沖突)的對象了。當(dāng)你調(diào)用 get() 取值時,直接就是正確的類型。這就是 Java 泛型的核心概念:你只需告訴編譯器要使用什么類型,剩下的細(xì)節(jié)交給它來處理。
1.一個元組類庫有時一個方法需要能返回多個對象。而 return 語句只能返回單個對象,解決方法就是創(chuàng)建一個對象,用它打包想要返回的多個對象。當(dāng)然,可以在每次需要的時候,專門創(chuàng)建一個類來完成這樣的工作。但是有了泛型,我們就可以一勞永逸。同時,還獲得了編譯時的類型安全。
這個概念稱為元組,它是將一組對象直接打包存儲于單一對象中。可以從該對象讀取其中的元素,但不允許向其中存儲新對象(這個概念也稱為 數(shù)據(jù)傳輸對象或 信使)。
通常,元組可以具有任意長度,元組中的對象可以是不同類型的。不過,我們希望能夠為每個對象指明類型,并且從元組中讀取出來時,能夠得到正確的類型。要處理不同長度的問題,我們需要創(chuàng)建多個不同的元組。下面是一個可以存儲兩個對象的元組:
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ù)傳入要存儲的對象。這個元組隱式地保持了其中元素的次序。
初次閱讀上面的代碼時,你可能認(rèn)為這違反了 Java 編程的封裝原則。a1 和 a2 應(yīng)該聲明為 private,然后提供 getFirst() 和 getSecond() 取值方法才對呀?考慮下這樣做能提供的 “安全性” 是什么:元組的使用程序可以讀取 a1 和 a2 然后對它們執(zhí)行任何操作,但無法對 a1 和 a2 重新賦值。例子中的 final 可以實現(xiàn)同樣的效果,并且更為簡潔明了。
2.一個堆棧類用鏈?zhǔn)浇Y(jié)構(gòu)實現(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)部類 Node 也是一個泛型,它擁有自己的類型參數(shù)。這個例子使用了一個 末端標(biāo)識 (end sentinel) 來判斷棧何時為空。這個末端標(biāo)識是在構(gòu)造 LinkedStack 時創(chuàng)建的。然后,每次調(diào)用 push() 就會創(chuàng)建一個 Node 對象,并將其鏈接到前一個 Node 對象。當(dāng)你調(diào)用 pop() 方法時,總是返回 top.item,然后丟棄當(dāng)前 top 所指向的 Node,并將 top 指向下一個 Node,除非到達(dá)末端標(biāo)識,這時就不能再移動 top 了。如果已經(jīng)到達(dá)末端,程序還繼續(xù)調(diào)用 pop() 方法,它只能得到 null,說明棧已經(jīng)空了。
泛型也可以應(yīng)用于接口。例如 生成器,這是一種專門負(fù)責(zé)創(chuàng)建對象的類。實際上,這是 工廠方法設(shè)計模式的一種應(yīng)用。不過,當(dāng)使用生成器創(chuàng)建新的對象時,它不需要任何參數(shù),而工廠方法一般需要參數(shù)。生成器無需額外的信息就知道如何創(chuàng)建新對象。
一般而言,一個生成器只定義一個方法,用于創(chuàng)建對象。例如 java.util.function 類庫中的 Supplier 就是一個生成器,調(diào)用其 get() 獲取對象。get() 是泛型方法,返回值為類型參數(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ù)的類型。CoffeeSupplier 同時還實現(xiàn)了 Iterable 接口,所以能用于 for-in 語句。不過,它還需要知道何時終止循環(huán),這正是第二個構(gòu)造函數(shù)的作用。
到目前為止,我們已經(jīng)研究了參數(shù)化整個類。其實還可以參數(shù)化類中的方法。類本身可能是泛型的,也可能不是,不過這與它的方法是否是泛型的并沒有什么關(guān)系。
泛型方法獨立于類而改變方法。作為準(zhǔn)則,請 “盡可能” 使用泛型方法。通常將單個方法泛型化要比將整個類泛型化更清晰易懂。
如果方法是 static 的,則無法訪問該類的泛型類型參數(shù),因此,如果使用了泛型類型參數(shù),則它必須是泛型方法。
要定義泛型方法,請將泛型參數(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);
}
} 
盡管可以同時對類及其方法進(jìn)行參數(shù)化,但這里未將 GenericMethods 類參數(shù)化。只有方法 f() 具有類型參數(shù),該參數(shù)由方法返回類型之前的參數(shù)列表指示。
對于泛型類,必須在實例化該類時指定類型參數(shù)。使用泛型方法時,通常不需要指定參數(shù)類型,因為編譯器會找出這些類型。這稱為 類型參數(shù)推斷。因此,對 f() 的調(diào)用看起來像普通的方法調(diào)用,并且 f() 看起來像被重載了無數(shù)次一樣。它甚至?xí)邮蹽enericMethods 類型的參數(shù)。
泛型方法和變長參數(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)庫的 java.util.Arrays.asList() 方法相同。
@SafeVarargs 注解保證我們不會對變長參數(shù)列表進(jìn)行任何修改,這是正確的,因為我們只從中讀取。如果沒有此注解,編譯器將無法知道這些并會發(fā)出警告。
2.一個泛型的 Supplier這是一個為任意具有無參構(gòu)造方法的類生成 Supplier 的類。為了減少鍵入,它還包括一個用于生成 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);
}
} 此類提供了產(chǎn)生以下對象的基本實現(xiàn):
static Tuple2f() {return new Tuple2<>("hi", 47);
} 4.一個 Set 工具對于泛型方法的另一個示例,請考慮由 Set 表示的數(shù)學(xué)關(guān)系。這些被方便地定義為可用于所有不同類型的泛型方法:
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));
}
} 前三個方法通過將第一個參數(shù)的引用復(fù)制到新的 HashSet 對象中來復(fù)制第一個參數(shù),因此不會直接修改參數(shù)集合。因此,返回值是一個新的 Set 對象。
這四種方法代表數(shù)學(xué)集合操作:union() 返回一個包含兩個參數(shù)并集的 Set ,intersection() 返回一個包含兩個參數(shù)集合交集的 Set ,difference() 從 superset中減去 subset 的元素,而 complement() 返回所有不在交集中的元素的 Set。
四、構(gòu)建復(fù)雜模型泛型的一個重要好處是能夠簡單安全地創(chuàng)建復(fù)雜模型。例如,我們可以輕松地創(chuàng)建一個元組列表:
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)生一個功能強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),而無需太多代碼。
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() “返回一個 TypeVariable 對象數(shù)組,表示泛型聲明中聲明的類型參數(shù)…” 這暗示你可以發(fā)現(xiàn)這些參數(shù)類型。但是正如上例中輸出所示,你只能看到用作參數(shù)占位符的標(biāo)識符,這并非有用的信息。
殘酷的現(xiàn)實是:在泛型代碼內(nèi)部,無法獲取任何有關(guān)泛型參數(shù)類型的信息。因此,你可以知道如類型參數(shù)標(biāo)識符和泛型邊界這些信息,但無法得知實際的類型參數(shù)從而用來創(chuàng)建特定的實例。在使用 Java 泛型工作時,它是必須處理的最基本的問題。
Java 泛型是使用擦除實現(xiàn)的。這意味著當(dāng)你在使用泛型時,任何具體的類型信息都被擦除了,你唯一知道的就是你在使用一個對象。因此,List
如果 Java 1.0 就含有泛型的話,那么這個特性就不會使用擦除來實現(xiàn)——它會使用具體化,保持參數(shù)類型為第一類實體,因此你就能在類型參數(shù)上執(zhí)行基于類型的語言操作和反射操作。泛型在 Java 中仍然是有用的,只是不如它們本來設(shè)想的那么有用,而原因就是擦除。
在基于擦除的實現(xiàn)中,泛型類型被當(dāng)作第二類類型處理,即不能在某些重要的上下文使用泛型類型。泛型類型只有在靜態(tài)類型檢測期間才出現(xiàn),在此之后,程序中的所有泛型類型都將被擦除,替換為它們的非泛型上界。例如,List 這樣的類型注解會被擦除為 List,普通的類型變量在未指定邊界的情況下會被擦除為 Object。
擦除的核心動機(jī)是你可以在泛化的客戶端上使用非泛型的類庫,反之亦然。這經(jīng)常被稱為 “遷移兼容性”。在理想情況下,所有事物將在指定的某天被泛化。在現(xiàn)實中,即使程序員只編寫泛型代碼,他們也必須處理 Java 5 之前編寫的非泛型類庫。這些類庫的作者可能從沒想過要泛化他們的代碼,或許他們可能剛剛開始接觸泛型。
因此 Java 泛型不僅必須支持向后兼容性——現(xiàn)有的代碼和類文件仍然合法,繼續(xù)保持之前的含義——而且還必須支持遷移兼容性,使得類庫能按照它們自己的步調(diào)變?yōu)榉盒停?dāng)某個類庫變?yōu)榉盒蜁r,不會破壞依賴于它的代碼和應(yīng)用。在確定了這個目標(biāo)后,Java 設(shè)計者們和從事此問題相關(guān)工作的各個團(tuán)隊決策認(rèn)為擦除是唯一可行的解決方案。擦除使得這種向泛型的遷移成為可能,允許非泛型的代碼和泛型代碼共存。
2.擦除的問題因此,擦除主要的正當(dāng)理由是從非泛化代碼到泛化代碼的轉(zhuǎn)變過程,以及在不破壞現(xiàn)有類庫的情況下將泛型融入到語言中。擦除允許你繼續(xù)使用現(xiàn)有的非泛型客戶端代碼,直至客戶端準(zhǔn)備好用泛型重寫這些代碼。這是一個崇高的動機(jī),因為它不會驟然破壞所有現(xiàn)有的代碼。
擦除的代價是顯著的。泛型不能用于顯式地引用運(yùn)行時類型的操作中,例如轉(zhuǎn)型、instanceof 操作和 new 表達(dá)式。因為所有關(guān)于參數(shù)的類型信息都丟失了,當(dāng)你在編寫泛型代碼時,必須時刻提醒自己,你只是看起來擁有有關(guān)參數(shù)的類型信息而已。考慮如下的代碼段:
class Foo{T var;
}
Foof = new Foo<>(); class Foo 中的代碼應(yīng)該知道現(xiàn)在工作于 Cat 之上。泛型語法也在強(qiáng)烈暗示整個類中所有 T 出現(xiàn)的地方都被替換,當(dāng)你在編寫這個類的代碼時,必須提醒自己:“不,這只是一個 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.邊界處的動作因為擦除,發(fā)現(xiàn)泛型最令人困惑的方面是可以表示沒有任何意義的事物。例如:
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 被存儲為 Class
如果我們創(chuàng)建一個集合而不是數(shù)組,情況就不同了:
public class ListMaker{Listcreate() {return new ArrayList<>();
}
public static void main(String[] args) {ListMakerstringMaker = new ListMaker<>();
ListstringList = stringMaker.create();
}
} 編譯器不會給出任何警告,盡管我們知道(從擦除中)在 create() 內(nèi)部的 newArrayList<>() 中的 被移除了——在運(yùn)行時,類內(nèi)部沒有任何
本例中這么做真的毫無意義嗎?如果在創(chuàng)建 List 的同時向其中放入一些對象呢,像這樣:
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);
}
} 
即使編譯器無法得知 add() 中的 T 的任何信息,但它仍可以在編譯期確保你放入FilledList 中的對象是 T 類型。因此,即使擦除移除了方法或類中的實際類型的信息,編譯器仍可以確保方法或類中使用的類型的內(nèi)部一致性。
因為擦除移除了方法體中的類型信息,所以在運(yùn)行時的問題就是邊界:即對象進(jìn)入和離開方法的地點。這些正是編譯器在編譯期執(zhí)行類型檢查并插入轉(zhuǎn)型代碼的地點。
因為擦除,我們將失去執(zhí)行泛型代碼中某些操作的能力。無法在運(yùn)行時知道確切類型:
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];
}
} 有時,我們可以對這些問題進(jìn)行編程,但是有時必須通過引入類型標(biāo)簽來補(bǔ)償擦除。這意味著為所需的類型顯式傳遞一個 Class 對象,以在類型表達(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()));
}
} 
編譯器來保證類型標(biāo)簽與泛型參數(shù)相匹配。
試圖在類中 new T() 是行不通的,部分原因是由于擦除,部分原因是編譯器無法驗證 T 是否具有默認(rèn)(無參)構(gòu)造函數(shù)。 Java 中的解決方案是傳入一個工廠對象,并使用該對象創(chuàng)建新實例。方便的工廠對象只是 Class 對象,因此,如果使用類型標(biāo)記,則可以使用 newInstance() 創(chuàng)建該類型的新對象:
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());
}
} 
對于 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 本身就是通過實現(xiàn) Supplier
另一種方法是模板方法設(shè)計模式。在以下示例中,create() 是模板方法,在子類中被重寫以生成該類型的對象:
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 字段,并通過無參構(gòu)造函數(shù)強(qiáng)制其初始化,該構(gòu)造函數(shù)又調(diào)用抽象的 create() 方法。這種創(chuàng)建方式可以在子類中定義,同時建立 T 的類型。
邊界允許我們對泛型使用的參數(shù)類型施加約束。盡管這可以強(qiáng)制執(zhí)行有關(guān)應(yīng)用了泛型類型的規(guī)則,但潛在的更重要的效果是我們可以在綁定的類型中調(diào)用方法。
由于擦除會刪除類型信息,因此唯一可用于無限制泛型參數(shù)的方法是那些 Object可用的方法。但是,如果將該參數(shù)限制為某類型的子集,則可以調(diào)用該子集中的方法。為了應(yīng)用約束,Java 泛型使用了 extends 關(guān)鍵字。
重要的是要理解,當(dāng)用于限定泛型類型時,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());
}
} 八、通配符你想在兩個類型間建立某種向上轉(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 的類型現(xiàn)在是 List extends Fruit>,你可以讀作 “一個具有任何從 Fruit繼承的類型的列表”。然而,這實際上并不意味著這個 List 將持有任何類型的 Fruit。通配符引用的是明確的類型,因此它意味著 “某種 flist 引用沒有指定的具體類型”。因此這個被賦值的 List 必須持有諸如 Fruit 或 Apple 這樣的指定類型,但是為了向上轉(zhuǎn)型為 Fruit,這個類型是什么沒人在意。
List 必須持有一種具體的 Fruit 或 Fruit 的子類型,但是如果你不關(guān)心具體的類型是什么,那么你能對這樣的 List 做什么呢?如果不知道 List 中持有的對象是什么類型,你怎能保證安全地向其中添加對象呢?就像在 CovariantArrays.java 中向上轉(zhuǎn)型一樣,你不能,除非編譯器而不是運(yùn)行時系統(tǒng)可以阻止這種操作的發(fā)生。你很快就會發(fā)現(xiàn)這個問題。
你可能認(rèn)為事情開始變得有點走極端了,因為現(xiàn)在你甚至不能向剛剛聲明過將持有Apple 對象的 List 中放入一個 Apple 對象。是的,但編譯器并不知道這一點。List extends Fruit>可能合法地指向一個 List
這里,可以聲明通配符是由某個特定類的任何基類來界定的,方法是指定<?super MyClass>,或者甚至使用類型參數(shù):<?super T>(盡管你不能對泛型參數(shù)給出一個超類型邊界;即不能聲明
public class SuperTypeWildcards {static void writeTo(List super Fruit>apples) {apples.add(new Apple());
apples.add(new Orange());
}
}2.無界通配符無界通配符>看起來意味著 “任何事物”,因此使用無界通配符好像等價于使用原生類型。事實上,編譯器初看起來是支持這種判斷的:
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());
}
}普通的類和方法只能使用特定的類型:基本數(shù)據(jù)類型或類類型。如果編寫的代碼需要應(yīng)用于多種類型,這種嚴(yán)苛的限制對代碼的束縛就會很大,泛型使我們能編寫更通用的代碼。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧
本文題目:JavaSE筆記——泛型-創(chuàng)新互聯(lián)
網(wǎng)址分享:http://chinadenli.net/article42/pejhc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制開發(fā)、關(guān)鍵詞優(yōu)化、做網(wǎng)站、網(wǎng)站建設(shè)、域名注冊、搜索引擎優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容