- 一般的類和方法,只能使用具體的類型,要麼是基本類型,要麼是自定義的類。如果要編寫可以適用於多種類型的代碼,這種刻板的限制對代碼的束縛就會很大。
- 在面向對象編程語言中,多態是一種泛化機制。
- JavaSE5 的重大變化之一: 泛型的概念。泛型實現了參數化類型的概念,使代碼可以應用於多種類型。泛型 這個術語的意思是 : 適用於許多許多的類型。
- 如果你瞭解其他語言(例如C++) 中的參數化類型機制,你就會發現,有些以前能夠做到的事情,使用Java泛型機制缺無法做到。使用別人構建好的泛型類型相當容易。但是如果你要自己創建一個泛型實例,就會遇到很多令人喫驚的事情。
- 這並非說Java泛型毫無用處。在很多情況下,它可以使代碼更直接更優雅。不過,瑞國你具備其他語言的經驗,而那種語言實現了更純粹的泛型,那麼,Java可能令你失望了。
Java泛型與C++的比較
-
Java的設計者曾說過,設計這門語言的靈感主要來自C++。
-
瞭解C++模板的某些方面,有助於你理解泛型的基礎。同時,非常重要的一點是,你可以瞭解Java泛型的侷限是什麼,以及爲什麼會有這些限制。最終目的是幫助你理解,Java泛型的邊界在哪裏。因爲你只有知道某個技術不能做到什麼,你這樣才能更好地做到所能做的。
-
Java社區中,人們普遍對C++模板有了一種誤解,而這種誤解可能會誤導你,令你在理解泛型的意圖時產生偏差。
簡單泛型
- 有很多原因促成了泛型的出現,最引人注目的一個原因是,就是爲了創造容器類。容器,就是存放要使用的對象的地方。數組也是如此,不過與簡單的數組相比,容器更加靈活,具備更多不同的功能。
//一個只能持有單個對象的類,這個類可以明確指定其持有的對象的類型 ,如下例子
public class Automobile {}
class Holder1{
private Automobile automobile;
public Holder1(Automobile automobile) {
this.automobile = automobile;
}
Automobile getAutomobile(){
return automobile;
}
}
- 這個類有以下問題 : ①可重用性不好 ②無法持有其他類型的任何對象
//JavaSE 5 之前,我們可以在類中直接持有Object類型的對象
public class Holder2 {
private Object object;
public Holder2(Object object) {
this.object = object;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public static void main(String[] args) {
Holder2 holder2 = new Holder2(new Automobile());
//把object 強制轉型爲 Automobile
Automobile automobile = (Automobile) holder2.getObject();
//設置對象的屬性
holder2.setObject("the object is Automobile");
//獲取對象的屬性
String string = (String) holder2.getObject();
holder2.setObject(1);
Integer a= (Integer) holder2.getObject();
}
}
- Holder2 可以存儲任何類型的對象,在這個例子中,只用了一個Holder2對象,卻先後存儲了三種不同類型的對象 (Automobile , String ,Integer 三個對象)。
- 有些時候,我們確實希望容器能夠同時持有多種類型的對象。但是,通常而言,我們只會使用容器來存儲一種類型的對象。泛型的主要目的之一就是指定容器要持有什麼類型的對象,而且由編譯器來保證類型的正確性。
public class Holder3 <T>{
private T t;
public Holder3(T t) {
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public static void main(String[] args) {
Holder3<Automobile> holder3 = new Holder3<Automobile>(new Automobile());
Automobile automobile = holder3.getT();
//如下會報錯
//holder3.setT(1); setT(參數必須爲 Automobile 類型)
}
}
- 當你創建Holder3 對象時,必須指明有什麼類型的對象,將其置於尖括號內。就像 main() 中那樣。然後,你就只能在 Holder3 中存入該類型(或其子類,因爲多態與泛型不衝突)的對象了。
- 在你從 Holder3 中取出它持有的對象時,自動地就是正確的類型。
- 這就是Java 泛型的核心概念: 告訴編譯器想使用什麼類型,然後編譯器幫你處理一切細節。
一個元組類庫
- 僅一次方法調用就能返回多個對象,你應該經常需要這樣的功能吧。可是 return 語句只允許返回單個對象,因此,解決辦法就是創建一個對象,用它來持有想要返回的多個對象。
- 當然,可以在每次需要的時候,專門創建一個類來完成這樣的工作。可是有了泛型,我們就能夠一次性地解決該問題,以後再也不用在這個問題上浪費時間了。同時,我們再編譯期就能確保類型安全。這個概念被稱爲 元組。
- 元組: 它是將一組對象直接打包存儲於其中的一個單一對象。這個容器對象允許讀取其中元素,但是不允許向其中存放新的對象。(這個概念也被稱爲 數據傳送對象或信使)
//如下 是一個2維元組,它能夠持有兩個東西
public class TwoTuple<A,B> {
public final A a;
public final B b;
public TwoTuple(A a, B b) {
this.a = a;
this.b = b;
}
@Override
public String toString() {
return "TwoTuple{" +
"a=" + a +
", b=" + b +
'}';
}
}
- 構造器捕獲了要存儲對象,而toString() 是一個便利函數,用來顯示列表中的值。注意,元組隱含地保持了其中元素的次序。
- 問題發現: 第一次閱讀上面的代碼時,你也許會想,這不是違反了 Java編程的安全性原則嗎?
- a 和 b 應該聲明爲 private ,然後提供 getA() 和 getB() 之類的訪問方法纔對呀? 讓我們仔細看看這個例子中的安全性: 客戶端程序可以讀取 a 和 b 對象,然後可以隨心所欲地使用 這兩個東西。但是 ,它們卻無法將其他賦值 賦予 a和 b 。因爲 final 聲明 爲你買了相同的安全保險,而且這種格式更簡潔明瞭。
- 還有另一種設計考慮,即你確實希望允許客戶端程序員改變 a 和 b 所引用的對象。然而, 採用以上的形式無疑是更安全的做法,這樣的話,如果程序員想要使用具體具有不同元素的元組,就強制要求他創建一個新的 TwoTuple 對象。
//我們可以利用繼承實現長度更長的元組
public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
public final C c;
public ThreeTuple(A a, B b, C c) {
super(a, b);
this.c = c;
}
@Override
public String toString() {
return "ThreeTuple{" +
"c=" + c +
", a=" + a +
", b=" + b +
'}';
}
}
class FourTuple<A, B, C, D> extends ThreeTuple<A, B, C> {
public final D d;
public FourTuple(A a, B b, C c, D d) {
super(a, b, c);
this.d = d;
}
@Override
public String toString() {
return "FourTuple{" +
"c=" + c +
", d=" + d +
", a=" + a +
", b=" + b +
'}';
}
}
class FiveTuple<A, B, C, D, E> extends FourTuple<A, B, C, D> {
public final E e;
public FiveTuple(A a, B b, C c, D d, E e) {
super(a, b, c, d);
this.e = e;
}
@Override
public String toString() {
return "FiveTuple{" +
"c=" + c +
", d=" + d +
", e=" + e +
", a=" + a +
", b=" + b +
'}';
}
}
- 爲了使用元組,你只需定義一個長度適合的元組,將其作爲方法的返回值,然後在 return 語句中創建該元組,並返回即可。
class Amphibian {}
class Vehicle {}
class TupleTest {
static TwoTuple<String, Integer> f() {
return new TwoTuple<>("hi", 47);
}
static ThreeTuple<Amphibian,String,Integer> g(){
return new ThreeTuple<>(new Amphibian(),"hi",47);
}
static FourTuple<Amphibian,String,Integer,Double> k(){
return new FourTuple<>(new Amphibian(),"hi",47,11.1);
}
static FiveTuple<Vehicle,Amphibian,String,Integer,Double> h(){
return new FiveTuple<>(new Vehicle(),new Amphibian(),"hi",47,11.1);
}
public static void main(String[] args) {
TwoTuple<String, Integer> f = f();
System.out.println(f);
//f.a="there"; compile error :final
System.out.println(g());
System.out.println(h());
System.out.println(k());
}
}
//運行結果爲
TwoTuple{a=hi, b=47}
ThreeTuple{c=47, a=generic.Amphibian@ea2f77, b=hi}
FiveTuple{c=hi, d=47, e=11.1, a=generic.Vehicle@1c7353a, b=generic.Amphibian@1a9515}
FourTuple{c=47, d=11.1, a=generic.Amphibian@f49f1c, b=hi}
- 由於有了泛型,你可以很容易地創建元組,令其返回一組任意類型的對象。而你所要做的,只是編寫表達式而已。
- 通過 f.a ="there" 語句的錯誤,我們可以看出,final 聲明確實能夠保護 public 元素,在對象被構造出來之後,聲明爲 final 的元素便不能被賦予其他值了。
一個堆棧類
public class LinkedStack<T> {
private static class Node<U> {
U u;
Node<U> next;
public Node() {
u = null;
next = null;
}
public Node(U u, Node<U> next) {
this.u = u;
this.next = next;
}
boolean end() {
return u == null && next == null;
}
}
private Node<T> node = new Node<>();
void push(T t) {
node = new Node<>(t, node);
}
T pop() {
T result = node.u;
if (!node.end())
node = node.next;
return result;
}
public static void main(String[] args) {
LinkedStack<String> linkedList = new LinkedStack<>();
for (String s :"Phasers or stun!".split(" ")) {
linkedList.push(s);
}
String s;
while((s=linkedList.pop()) != null){
System.out.println(s);
}
}
}
//運行結果爲
stun!
or
Phasers
- 內部類 Node 也是一個泛型,它擁有自己的類型參數。
- 這個 例子使用了一個 末端哨兵 來判斷堆棧何時爲空。這個 末端哨兵 是在構造 LinkedStack 時創建的。
- 然後每調用一次 push() 方法,就會創建一個Node<T> 對象,並將其鏈接到前一個 Node<T>對象。當你調用 pop() 方法時,總是返回 node.u ,然後丟棄當前 node 所指的 Node<T> , 並將 node 轉移到下一個 Node<T> ,除非你已經碰到了 末端哨兵,這時候就不再移動 node 了。
- 如果已經到了末端,客戶端程序還繼續調用 pop() 方法,它只能得到 null, 說明堆棧已經空了。
RandomList
- 假設我們需要一個持有特定類型對象的列表,每次調用其上的 select() 方法時,它可以隨機選取一個元素。
public class RandomList<T> {
private List<T> lists=new ArrayList<>();
private Random random=new Random(47);
public void add(T t){
lists.add(t);
}
T select(){
return lists.get(random.nextInt(lists.size()));
}
public static void main(String[] args) {
RandomList<String> randomList = new RandomList<>();
for (String s :"The quick brown fox jumped over ".split(" ")) {
randomList.add(s);
}
for (int i = 0; i < 5; i++) {
System.out.println(randomList.select());
}
}
}
//運行結果爲
brown
over
quick
over
quick
泛型接口
- 泛型也可以應用於接口。例如生成器 ,這是一種專門負責創建對象的類。實際上,這是工廠方法設計模式的一種應用。
public interface Generator<T> {
T next();
}
- 方法 next() 的返回類型是參數化的 T 。正如你所見的,接口使用泛型與類使用泛型沒什麼區別。
class Coffee {
static long counter = 0;
final long id = counter++;
@Override
public String toString() {
return getClass().getSimpleName()+" "+id;
}
}
class Latte extends Coffee {
}
class Mocha extends Coffee {
}
class Cappuccino extends Coffee {
}
class Americano extends Coffee {
}
class Breve extends Coffee {
}
- 現在我們可以編寫一個類,實現 Generator<Coffee> 接口,它能隨機生成不同類型的 Coffee對象。
class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> {
private Class[] classes = {
Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class
};
private Random random = new Random(47);
//無參構造器
public CoffeeGenerator() {
}
//for iteration 迭代
private int size = 0;
//有參構造器
public CoffeeGenerator(int size) {
this.size = size;
}
@Override
public Coffee next() {
try {
return (Coffee) classes[random.nextInt(classes.length)].newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
class CoffeeIterator implements Iterator<Coffee> {
int count = size;
@Override
public boolean hasNext() {
return count > 0;
}
@Override
public Coffee next() {
count --;
return CoffeeGenerator.this.next();
}
//not implemented 未實現
@Override
public void remove(){
throw new UnsupportedOperationException();
}
}
@Override
public Iterator<Coffee> iterator() {
return new CoffeeIterator();
}
public static void main(String[] args) {
CoffeeGenerator coffees = new CoffeeGenerator();
for (int i = 0; i < 5; i++) {
System.out.println(coffees.next());
}
for (Coffee coffee:new CoffeeGenerator(5)) {
System.out.println(coffee);
}
}
}
//運行結果爲
Americano 0
Latte 1
Americano 2
Mocha 3
Mocha 4
Americano 5
Latte 6
Americano 7
Mocha 8
Mocha 9
- 參數化的 Generator 接口確保 next() 的返回值是參數的類型。
- CoffeeGenerator 同時還實現了 Iterator 接口,所以它可以在循環語句中使用。不過,它還需要一個 末端哨兵 來判斷何時停止,這正是第二個構造器的功能。
public class Fibonacci implements Generator<Integer> {
private int count = 0;
@Override
public Integer next() {
return fib(count++);
}
private int fib(int n){
if (n<2) return 1;
return fib(n-1)+fib(n-1);
}
public static void main(String[] args) {
Fibonacci fibonacci = new Fibonacci();
for (int i = 0; i < 5; i++) {
System.out.println(fibonacci.next()+" ");
}
}
}
//運行結果爲
1
1
2
4
8
- 雖然我們再 Fibonacci類裏裏歪歪使用的都是 int 類型,但是其類型參數確實 Integer。這個例子引出了Java 泛型的一個侷限性: 基本類型無法作爲類型參數。
- 不過,Java SE5 具備了自動打開和自動拆包的功能, 可以很方便地在基本和其相應的包裝器類型之間進行轉換。通過這個例子中 Fibonacci 類對 int 的使用,我們已經看到了這種效果。
- 如果還想進一步,編寫一個實現了 Iterator 的Fibonacci 生成器。如下例子。
public class IteratorFibonacci extends Fibonacci implements Iterable<Integer> {
private int n;
public IteratorFibonacci(int n) {
this.n = n;
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
@Override
public boolean hasNext() {
return n>0;
}
@Override
public Integer next() {
n--;
return IteratorFibonacci.this.next();
}
};
}
public static void main(String[] args) {
IteratorFibonacci fibonacci = new IteratorFibonacci(6);
for (int s : fibonacci) {
System.out.println(s+" ");
}
}
}
//運行結果爲
1
1
2
4
8
16
- 如果要在循環語句中使用 IterableFibonacci ,必須向 IterableFibonacci的構造器提供一個邊界值,然後 hasNext() 方法才能知道何時應該返回false。
泛型方法
- 到目前爲止,我們看到的泛型,都是應用於整個類上。但同樣可以在類中包含參數化方法,而這個方法所在的類可以是泛型類,也可以不是泛型類。也就是說,是否擁有泛型方法,與其所在的類是否是泛型沒有關係。
- 泛型方法使得該方法能夠獨立於類而產生變化。以下時一個基本的知道原則: 無論何時,只要你能做到,你就應該儘量使用泛型方法。
- 也就是說,如果使用泛型方法可以取代整個類泛型化,那麼你就應該只使用泛型方法。因爲它可以使事情更清楚明白。
- 另外,對於一個 static 的方法而言, 無法訪問泛型類的類型參數,所以,如果static 需要使用泛型能力,就必須使其成爲泛型方法。
public class GenericMethods {
public <T> void f(T x){
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods methods = new GenericMethods();
//傳入字符串
methods.f(" ");
//傳入int類型
methods.f(2);
//傳入double
methods.f(2.2d);
//傳入float
methods.f(2.2f);
//傳入對象
methods.f(methods);
}
}
//運行結果爲
java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
generic.GenericMethods
- GenericMethods 並不是參數化,儘管這個類和其內部的方法可以被同時參數化,但是在這個例子中,只有方法 f() 擁有類型參數。這是由該方法的返回類型前面的類型參數列表指明的。
- 注意,當使用泛型類時,必須在創建對象的時候指定類型參數的值,而使用泛型方法時候,通常不必指明參數類型,因爲編譯器會爲我們找出具體的類型。這被稱爲 類型參數推斷。
- 我們可以像調用普通方法一樣調用 f() 方法,而且就好像是f() 方法被無限次地重載過。它甚至可以接受 GenericMethods 作爲其類型參數。
- 如果調用 f() 時傳入基本類型,自動打包機制就會介入其中,將基本類型的值包裝爲對應的對象。事實上,泛型方法與自動打包避免了許多以前我們不得不自己編寫出來的代碼。(如 int的 值被打包機制轉化爲 Integer)。
槓桿利用類型參數推斷
- 人們對泛型有一個抱怨,使用泛型有時候需要向程序中加入更多的代碼。如下
Map<Person,List<? extends Pest>> petPeople=new HasMap<Person,List<? extends Person>>(;)
- 如上代碼,我們總是在重複自己做過的事情,編譯器本來應該能夠從泛型參數列表中的一個參數推斷出另一個參數。可惜的是,編譯器暫時還做不到。然泛型方法中,類型參數推斷可以爲我們簡化一部分工作。
- 例如,我們可以編寫一個工具類,它包含各種各樣的 static 方法,專門用來創建各種常用的容器對象。
public class New {
static <K, V> Map<K, V> map() {
return new HashMap<>();
}
static <T> List<T> list() {
return new ArrayList<>();
}
static <T> LinkedList<T> linkedList() {
return new LinkedList<>();
}
static <T> Set<T> set() {
return new HashSet<>();
}
static <T> Queue<T> queue() {
return new LinkedList<>();
}
public static void main(String[] args) {
Map<String, List<String>> map = New.map();
List<String> list = New.list();
Set<String> set = New.set();
LinkedList<String> linkedList = New.linkedList();
Queue<String> queue = New.queue();
}
}
- main() 方法演示瞭如何使用這個工具類,類型參數推斷避免了重複的泛型參數列表。它同樣可以應用於 如下
Map<Person,List<? extends Pet>> map=New.Map();
//...
- 如果某人閱讀以上代碼,他必須分析理解工具類 New ,以及New 所隱含的功能。而這似乎與不使用 New 時(具有重複的類型參數列表的定義)的工作效率差不多。
- 這真夠諷刺的,要知道,我們引入New 工具類的目的,正是爲了使代碼簡單易讀。不過,如果 標準的Java類庫要是能添加類似 New.java 這樣的工具類的話,我們還是應該使用這樣的工具類。
- Java 在判斷類型的時候, 往往是在賦值操作的時候,能夠通過參數化類判斷出具體的類型。
public class LimitsOfInference {
public static <T> List<T> f() {
return new ArrayList<>();
}
public static void m(List<String> list){
System.out.println(list.getClass().getName());
}
public static void main(String[] args) {
List<String> objects = f();
m(objects);
}
}
//運行結果爲
java.util.ArrayList
- 通過 List<String> 得知了返回的ArrayList<T> 中 T的類型爲String ,因此 m 方法直接調用得到 運行結果。
- 如果 m() 方法直接調用 f() 方法,而不知道應該返回什麼類型,默認是返回Object類型。
- 以ArrayList<String> 爲參數,編譯器不會自動轉換,而是依然向裏面傳入 ArrayList<Object> 因此報錯如下
//這裏編譯器永遠不會把 Object 轉化爲 String
public class GenericDemo {
static ArrayList<String> f(){
return new ArrayList<>();
}
static void m(List<Object> list){
System.out.println(list.getClass().getName());
}
public static void main(String[] args) {
//通過泛型方法及類型推斷的到 ArrayList<String>
ArrayList<String> strings = f();
// m(strings); m() 需要的是 List<Object> 集合而不是 List<String>集合
// m(f()); 同上
}
}
- 通過上面兩段代碼可知:
- 泛型方法在獲取具體類型時,僅僅在賦值的時候有效,參數中不會自動轉換。
//注意 不要將這兩種情況搞混淆
//1 泛型賦值
//2 類繼承使用
public class GenericDemo2 {
static String f() {
return new String();
}
static void m(Object object){
System.out.println(object.getClass().getName());
}
public static void main(String[] args) {
m(f());
}
}
//運行結果
java.lang.String
顯式的類型說明
- 在泛型方法中,可以顯式地指明類型,不過這種語法很少使用。要顯式地指明類型,必須在點操作符與方法名之間插入尖括號,然後把類型置於尖括號內。
- 如果是在定義該方法的類的內部,必須在點操作符之前使用 this 關鍵字,如果是使用static 的方法,必須在點操作符之前加上類型。
public class ExplicitTypeSpecification {
static void f(Map<Person, List<? extends Person>> map) {
}
public static void main(String[] args) {
Map<Object, Object> map = New.map();
//f(map); 編譯失敗 無法把 Object 類型轉化爲 Person對象
f(New.<Person, List<? extends Person>>map());
//可以直接使用如下
f(New.map());
}
}
- 當然,這種語法抵消了 New 類爲我們帶來的好處(即省去了大量的類型說明),不過,只有在編寫非賦值語句時,我們才需要這樣的額外說明。
可變參數與泛型方法
- 泛型方法與可變參數列表能夠很好地共存。
public class GenericVarargs {
static <T> List<T> makeList(T... args) {
ArrayList<T> list = new ArrayList<>();
Arrays.asList(args).forEach(each -> {
list.add(each);
});
return list;
}
public static void main(String[] args) {
List<String> list = makeList("A");
System.out.println(list);
list = makeList("A", "B", "C");
System.out.println(list);
list = makeList("QWERTYUIOPASDFGHJK".split(""));
System.out.println(list);
}
}
//運行結果
[A]
[A, B, C]
[Q, W, E, R, T, Y, U, I, O, P, A, S, D, F, G, H, J, K]
- makeList() 方法展示了 與 標準類庫中 Arrays.asList() 方法
利用Generator的泛型方法
- 利用生成器,我們可以很方便地填充一個 Collection ,而泛型化這種操作是具有實際意義的。
public class Generators {
static <T> Collection<T> fill(Collection<T> collection,
Generator<T> generator,
int n) {
for (int i = 0; i < n; i++)
collection.add(generator.next());
return collection;
}
public static void main(String[] args) {
Collection<Coffee> coffees = fill(new ArrayList<Coffee>(), new CoffeeGenerator(), 4);
for (Coffee c : coffees) {
System.out.println(c);
}
Collection<Integer> collection=fill(new ArrayList<Integer>(), new Fibonacci(), 12);
for (int i : collection) {
System.out.println(i);
}
}
}
//運行結果爲
Americano 0
Latte 1
Americano 2
Mocha 3
1
1
2
4
8
16
32
64
128
256
512
1024
- 請注意,fill() 方法是如何透明地應用於 Coffee 和 Integer 的容器和 生成器。
一個通用的 Generator
- 可以爲任何類構造一個Generator , 只要該類具有默認的構造器。爲了減少類型聲明,它提供一個泛型方法,用於生成BasicGeneratror。
public class BasicGenericator<T> implements Generator<T> {
private Class<T> type;
public BasicGenericator(Class<T> type) {
this.type = type;
}
@Override
public T next() {
try {
return type.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static <T> Generator<T> create(Class<T> type) {
return new BasicGenericator<T>(type);
}
}
- 這個類提供了一個基本實現,用於生成某個類的對象。這個類具備兩個特點
- 它必須聲明爲 public (因爲 BasicGenerator 與要處理的類在不同的包中,所以該類必須聲明爲 public ,並且不只具有包內訪問權限)。
- 它必須具備默認的構造器,(無參數的構造器)。
public class CountedObject {
private static long counter = 0;
private final long id =counter++;
public long getId(){
return id;
}
@Override
public String toString() {
return "CountedObject"+id;
}
}
class BasicGeneratorDemo{
public static void main(String[] args) {
Generator<CountedObject> generator= BasicGenericator.create(CountedObject.class);
for (int i = 0; i < 5; i++) {
System.out.println(generator.next());
}
}
}
//運行結果
CountedObject0
CountedObject1
CountedObject2
CountedObject3
CountedObject4
- 可以看到 ,使用泛型方法創建Generator對象,大大減少了我們要編寫的代碼。
- Java泛型要求傳入Class對象,以便也可以在 create()中用它進行類型推斷。