在從Java in a Nutshell,5th edith中摘錄的兩部部分中的第一部分,David Flanagan描述了如何使用泛型。這部分,David Flanagan將具體告訴你如何創(chuàng )建自己的泛型和泛型方法,并且以Java核心API很多重要的泛型作為結束總結
Java中的泛型 第二部分作者:David Flanagan譯者:glorywine版權聲明:任何獲得Matrix授權的網(wǎng)站,轉載時(shí)請務(wù)必以超鏈接形式標明文章原始出處和作者信息及本聲明作David Flanagan;
glorywine原文地址:
http://www.onjava.com/pub/a/onjava/excerpt/javaian5_chap04/index1.html中文地址:
http://www.matrix.org.cn/resource/article/43/43914_Generic_Types.html關(guān)鍵詞: Java Generic Types
編者注:在從Java in a Nutshell,5th edith中摘錄的兩部部分中的
第一部分,David Flanagan描述了如何使用泛型。這部分,David Flanagan將具體告訴你如何創(chuàng )建自己的泛型和泛型方法,并且以Java核心API很多重要的泛型作為結束總結。
創(chuàng )建泛型和泛型方法創(chuàng )建一個(gè)簡(jiǎn)單的泛型是非常容易的。首先,在一對尖括號(< >)中聲明類(lèi)型變量,以逗號間隔變量名列表。在類(lèi)的實(shí)例變量和方法中,可以在任何類(lèi)型的地方使用那些類(lèi)型變量。切記,類(lèi)型變量?jì)H在編譯時(shí)存在,所以不能使用instanceof和new這類(lèi)運行時(shí)操作符來(lái)操作類(lèi)型變量。
讓我們以一個(gè)簡(jiǎn)單的例子來(lái)開(kāi)始這部分的學(xué)習,而后將精簡(jiǎn)這個(gè)例子。這段代碼定義了一個(gè)樹(shù)形數據結構,使用類(lèi)型變量V代表存儲在各個(gè)樹(shù)結點(diǎn)中的值。
import java.util.*;
/**
* A tree is a data structure that holds values of type V.
* Each tree has a single value of type V and can have any number of
* branches, each of which is itself a Tree.
*/
public class Tree<V> {
// The value of the tree is of type V.
V value;
// A Tree<V> can have branches, each of which is also a Tree<V>
List<Tree<V>> branches = new ArrayList<Tree<V>>();
// Here‘s the constructor. Note the use of the type variable V.
public Tree(V value) { this.value = value; }
// These are instance methods for manipulating the node value and branches.
// Note the use of the type variable V in the arguments or return types.
V getValue() { return value; }
void setValue(V value) { this.value = value; }
int getNumBranches() { return branches.size(); }
Tree<V> getBranch(int n) { return branches.get(n); }
void addBranch(Tree<V> branch) { branches.add(branch); }
}
正如你所看到的,命名一個(gè)類(lèi)型變量習慣于一個(gè)大寫(xiě)字母。使用一個(gè)字母可以同現實(shí)中那些具有描述性的,長(cháng)的實(shí)際變量名有所區別。使用大寫(xiě)字母要同變量命名規則一致,并且要區別于局部變量,方法參數,成員變量,而這些變量常常使用一個(gè)小寫(xiě)字母。集合類(lèi)中,比如java.util中常常使用類(lèi)型變量E代表“Element type”。T和S常常用來(lái)表示范型變量名(好像使用i和j作為循環(huán)變量一樣)。
注意到,當一個(gè)變量被聲明為泛型時(shí),只能被實(shí)例變量和方法調用(還有內嵌類(lèi)型)而不能被靜態(tài)變量和方法調用。原因很簡(jiǎn)單,參數化的泛型是一些實(shí)例。靜態(tài)成員是被類(lèi)的實(shí)例和參數化的類(lèi)所共享的,所以靜態(tài)成員不應該有類(lèi)型參數和他們關(guān)聯(lián)。方法,包括靜態(tài)方法,可以聲明和使用他們自己的類(lèi)型參數,但是,調用這樣一個(gè)方法,可以被不同地參數化。這些內容將在本章后面談到。
類(lèi)型變量綁定上面例子中的Tree<V>中的類(lèi)型變量V是不受約束的:Tree可以被參數化為任何類(lèi)型。以前我們常常會(huì )設置一些約束條件在需要使用的類(lèi)型上:也許我們需要強制一個(gè)類(lèi)型參數實(shí)現一個(gè)或多個(gè)接口,或是一個(gè)特定類(lèi)的子類(lèi)。這可以通過(guò)指明類(lèi)型綁定來(lái)完成。我們已經(jīng)看到了統配符的上界,而且使用簡(jiǎn)單的語(yǔ)法可以指定一般類(lèi)型變量的上界。后面的代碼,還是使用Tree這個(gè)例子,并且通過(guò)實(shí)現Serializable和Comparable來(lái)重寫(xiě)。為了做到這點(diǎn),例子中使用類(lèi)型變量綁定來(lái)確保值類(lèi)型的Serializable和Comparable。
import java.io.Serializable;
import java.util.*;
public class Tree<V extends Serializable & Comparable<V>>
implements Serializable, Comparable<Tree<V>>
{
V value;
List<Tree<V>> branches = new ArrayList<Tree<V>>();
public Tree(V value) { this.value = value; }
// Instance methods
V getValue() { return value; }
void setValue(V value) { this.value = value; }
int getNumBranches() { return branches.size(); }
Tree<V> getBranch(int n) { return branches.get(n); }
void addBranch(Tree<V> branch) { branches.add(branch); }
// This method is a nonrecursive implementation of Comparable<Tree<V>>
// It only compares the value of this node and ignores branches.
public int compareTo(Tree<V> that) {
if (this.value == null && that.value == null) return 0;
if (this.value == null) return -1;
if (that.value == null) return 1;
return this.value.compareTo(that.value);
}
// javac -Xlint warns us if we omit this field in a Serializable class
private static final long serialVersionUID = 833546143621133467L;
}
一個(gè)類(lèi)型變量的綁定是通過(guò)extends后的名字和一個(gè)類(lèi)型列表(這可以是參數化的,就像Comparable一樣)表達的。注意當有不止一個(gè)綁定時(shí),就像上面例子中的,綁定的類(lèi)型要用&作為分隔符,而不是使用逗號。都后用來(lái)分隔類(lèi)型變量,如果用來(lái)分隔類(lèi)型變量綁定,就會(huì )模棱兩可。一個(gè)類(lèi)型變量可以有任何數量的綁定,包括任何數量的借口和至多一個(gè)類(lèi)。
范型中的通配符上一章的例子中我們看到了通配符和控制參數化類(lèi)型的通配符綁定。這些在范型中同樣非常有用。當前設計的Tree要求每個(gè)節點(diǎn)有相同類(lèi)型的值,V。也許這樣太嚴格了,也許我們應該讓Tree的branches能夠存放V的子類(lèi)而不全是V。這個(gè)版本的Tree(刪除了Comparable和Serializable接口的實(shí)現)這樣做會(huì )更靈活。
public class Tree<V> {
// These fields hold the value and the branches
V value;
List<Tree<? extends V>> branches = new ArrayList<Tree<? extends V>>();
// Here‘s a constructor
public Tree(V value) { this.value = value; }
// These are instance methods for manipulating value and branches
V getValue() { return value; }
void setValue(V value) { this.value = value; }
int getNumBranches() { return branches.size(); }
Tree<? extends V> getBranch(int n) { return branches.get(n); }
void addBranch(Tree<? extends V> branch) { branches.add(branch); }
}通配符綁定允許我們在枝節點(diǎn)上增加一個(gè)Tree<Integer>,比如,一個(gè)樹(shù)枝Tree<Number>:
Tree<Number> t = new Tree<Number>(0); // Note autoboxing
t.addBranch(new Tree<Integer>(1)); // int 1 autoboxed to Integer
通過(guò)getBranch()查詢(xún)樹(shù)枝,而樹(shù)枝的返回類(lèi)型不知道,所以必須使用統配符來(lái)表達。接下來(lái)的兩個(gè)是合法的,但第三個(gè)不是:
Tree<? extends Number> b = t.getBranch(0);
Tree<?> b2 = t.getBranch(0);
Tree<Number> b3 = t.getBranch(0); // compilation error
當我們這樣來(lái)查詢(xún)一個(gè)樹(shù)枝時(shí),不能精確確定它的返回類(lèi)型,但是存在類(lèi)型的上限,所以,我們可以這樣做:
Tree<? extends Number> b = t.getBranch(0);
Number value = b.getValue();
那我們不能做什么呢?設定樹(shù)枝的值,或者在原有的樹(shù)枝上添加新的樹(shù)枝。早前章節解釋的,上界的存在不會(huì )改變返回值的類(lèi)型不可知,編譯器沒(méi)有足夠的信息讓我們安全的給setValue()或者一個(gè)樹(shù)枝(包括值類(lèi)型)的addBranch()傳遞一個(gè)值。下面的兩行代碼都是非法的:
b.setValue(3.0); // Illegal, value type is unknown
b.addBranch(new Tree<Double>(Math.PI));
這個(gè)例子在設計時(shí)找到了一個(gè)平衡點(diǎn):使用綁定通配符使得數據結構更加靈活,但是減少了安全使用其中方法的可能。這個(gè)設計是好是壞就要根據上下文聯(lián)系了。通常,好的范型設計是非常困難的。幸運的是,大多我們要使用的已經(jīng)在java.util包中設計好了,而不用我們自己再去設計。
范型方法 正如前面說(shuō)的,范型只能被實(shí)例成員調用,而不是靜態(tài)成員。同實(shí)例方法一樣,靜態(tài)方法也可以使用通配符。盡管靜態(tài)方法不能使用包含他們的類(lèi)中的類(lèi)型變量,但是他們可以聲明自己的類(lèi)型變量。當一個(gè)方法聲明了自己的類(lèi)型變量,就叫做范型方法。
這里有一個(gè)要添加到Tree中的靜態(tài)方法。他不是一個(gè)范型方法,但是使用了綁定的通配符,就好像先前我們看到的sumList()一樣:
/** Recursively compute the sum of the values of all nodes on the tree */
public static double sum(Tree<? extends Number> t) {
double total = t.value.doubleValue();
for(Tree<? extends Number> b : t.branches) total += sum(b);
return total;
}
通過(guò)通配符的上界綁定,聲明自己的類(lèi)型變量來(lái)重寫(xiě)這個(gè)方法:
public static <N extends Number> double sum(Tree<N> t) {
N value = t.value;
double total = value.doubleValue();
for(Tree<? extends N> b : t.branches) total += sum(b);
return total;
} 范型的sum()不比通配符版本的簡(jiǎn)單,而且聲明變量并沒(méi)有讓我們獲得什么。這種情況下,通配符方案要比范型方法更有效,當一個(gè)類(lèi)型變量用來(lái)表達兩個(gè)參數之間或者參數和返回值之間的關(guān)系時(shí),范型方法才是需要的。請看下面的例子:
// This method returns the largest of two trees, where tree size
// is computed by the sum() method. The type variable ensures that
// both trees have the same value type and that both can be passed to sum().
public static <N extends Number> Tree<N> max(Tree<N> t, Tree<N> u) {
double ts = sum(t);
double us = sum(u);
if (ts > us) return t;
else return u;
}
這個(gè)方法使用類(lèi)型變量N來(lái)約束參數和返回值有相同類(lèi)型,并且參數是Number或者他的子類(lèi)。
使得參數具有相同類(lèi)型也許是有爭議的,應該讓我們能調用max()不論是Tree<Integer>或者Tree<Double>。一種方法是使用兩個(gè)不相干的類(lèi)型變量來(lái)表示兩個(gè)不相干的值類(lèi)型。注意,我們不能在方法的返回時(shí)使用變量而必須使用通配符:
public static <N extends Number, M extends Number>
Tree<? extends Number> max(Tree<N> t, Tree<M> u) {...}
既然兩個(gè)類(lèi)型變量N和M沒(méi)有任何聯(lián)系,而且每個(gè)僅在簽名的時(shí)候使用,他們沒(méi)有提供比通配符更多的好處,這種方法最好這樣寫(xiě):
public static Tree<? extends Number> max(Tree<? extends Number> t,
Tree<? extends Number> u) {...}
所有在這里的范型方法都是靜態(tài)的,這并不是必須的,實(shí)例方法也可以聲明自己的類(lèi)型變量。
調用范型方法當你使用范型時(shí),必須指定實(shí)際類(lèi)型參數來(lái)代替相應的類(lèi)型變量。但這些對范型方法有些不同:編譯器總是能計算出基于你所傳遞的參數的相應范型方法參數??紤]一下上面定義的max(),作為例子:
public static <N extends Number> Tree<N> max(Tree<N> t, Tree<N> u) {...}當你調用這個(gè)方法時(shí),不需要指明N,因為N是隱含地由t和u指明。在后面的代碼中,編譯器決定N為Integer:
Tree<Integer> x = new Tree<Integer>(1);
Tree<Integer> y = new Tree<Integer>(2);
Tree<Integer> z = Tree.max(x, y);
編譯器判斷范型方法的參數類(lèi)型稱(chēng)為類(lèi)型推斷。類(lèi)型推斷是相對于知覺(jué)推斷的。而實(shí)際編譯器的實(shí)現方法是一種非常復雜的過(guò)程,超過(guò)了這本書(shū)的討論范圍。更多的細節在The Java Language Specification, Third Edition的第十五章。
讓我們看一個(gè)更加復雜的類(lèi)型推斷,考慮一下這個(gè)方法:
public class Util {
/** Set all elements of a to the value v; return a. */
public static <T> T[] fill(T[] a, T v) {
for(int i = 0; i < a.length; i++) a[i] = v;
return a;
}
}這里有兩個(gè)該方法的調用:
Boolean[] booleans = Util.fill(new Boolean[100], Boolean.TRUE);
Object o = Util.fill(new Number[5], new Integer(42));
在第一個(gè)例子中,編譯器可以輕松的推斷出T是Boolean類(lèi)型,第二個(gè)例子中,編譯器判斷T是Number。
在非常罕見(jiàn)的情況下,你可能會(huì )顯示的指明范型方法的參數類(lèi)型。有時(shí)候這是必要的,比如,當范型方法不需要參數時(shí)??紤]一下java.util.Collections.emptySet():返回一個(gè)空集合,但是不同于Collections.singleton()(可以在參考部分察看),他不帶任何參數,但需要指明返回類(lèi)型。通過(guò)在方法名前的<>中,可以顯示的指明參數類(lèi)型:
Set<String> empty = Collections.<String>emptySet();
類(lèi)型參數不能同沒(méi)有限制的方法名結合使用:他們必須跟隨在一個(gè).后或者在關(guān)鍵字new后,或者在關(guān)鍵字this前,或者構造函數的super前。
可以證明,如果如果你將Collections.emptySet()的返回值賦給一個(gè)變量,就像我們上邊通過(guò)類(lèi)型推斷機制推斷基于變量類(lèi)型的參數類(lèi)型。盡管顯示的類(lèi)型說(shuō)明可以更加清楚,但這不是必要的,可以像下面一樣重寫(xiě):
Set<String> empty = Collections.emptySet();
在方法調用表達式中,顯示的說(shuō)明emptySet()的返回值類(lèi)型是必要的。比如,假設你要調用一個(gè)名為printWords()的方法,該方法僅需一個(gè)Set<String>的參數,如果你想傳遞一個(gè)空的集合給該方法,就要像下面一樣寫(xiě):
printWords(Collections.<String>emptySet());
這種情況下,顯示的類(lèi)型說(shuō)明是必要的。
范型方法和數組早先我們看到,編譯器不允許創(chuàng )建一個(gè)類(lèi)型參數化的數組。但是對于范型的使用會(huì )是不同的??紤]一下前面定義的Util.fill(),它得以第一個(gè)參數和返回值類(lèi)型都是T[]。而方法體內不必創(chuàng )建任何參數為T(mén)的數組,所以這個(gè)方法是合法的。
如果你創(chuàng )建一個(gè)方法使用varargs(參見(jiàn)第二章的2.6.4)和類(lèi)型變量,記住調用varargs隱含創(chuàng )建一個(gè)數組,請看下面的例子:
/** Return the largest of the specified values or null if there are none */
public static <T extends Comparable<T>> T max(T... values) { ... }
你可以使用一個(gè)Integer類(lèi)型來(lái)調用這個(gè)方法,因為編譯器會(huì )在調用的時(shí)候插入必要的數組創(chuàng )建代碼。但是你不能將參數轉換為Comparable<Integer>來(lái)調用這個(gè)方法,因為創(chuàng )建一個(gè)Comparable<Integer>[]是不合法的。
參數化異常異常是在運行時(shí)拋出和捕獲的。沒(méi)有辦法讓編譯器完成類(lèi)型檢查,來(lái)保證在catch塊中拋出的未知的類(lèi)型匹配異常。由于這個(gè)原因,catch塊很可能不包含類(lèi)型變量和通配符。既然不可能保證在運行時(shí)捕獲一個(gè)編譯器時(shí)類(lèi)型參數完整性異常,所以不允許創(chuàng )建任何Throwable類(lèi)型的子類(lèi)。參數化異常是不允許的。
但是你可以使用類(lèi)型變量在throw塊里的方法簽名中??纯聪旅娴睦樱?br>
public interface Command<X extends Exception> {
public void doit(String arg) throws X;
}這個(gè)接口描述了一個(gè)“command”:一塊代碼只有一個(gè)String類(lèi)型的參數,沒(méi)有返回值。代碼可能拋出一個(gè)類(lèi)型為X的異常。這里有一個(gè)例子使用這個(gè)接口:
Command<IOException> save = new Command<IOException>() {
public void doit(String filename) throws IOException {
PrintWriter out = new PrintWriter(new FileWriter(filename));
out.println("hello world");
out.close();
}
};
try { save.doit("/tmp/foo"); }
catch(IOException e) { System.out.println(e); }范型個(gè)案研究:比較和枚舉Java1.5引入的范型新特性,在1.5的API中有使用,特別多的是在java.util包中,但是在java.lang,java.lang.reflect和java.util.concurrent中也有。這些API都是經(jīng)過(guò)仔細的斟酌創(chuàng )建的,通過(guò)學(xué)習這些API我們可以學(xué)到很多好的設計方法。
java.util中的范形是比較簡(jiǎn)單的:因為大多都是集合類(lèi),類(lèi)型變量也是代表集合中的元素。java.lang中的幾個(gè)重要范型是比較難以理解的,他們不是集合,而且第一眼很不容易理解為什么設計成范型。學(xué)習這些范型可以讓我們更深層次的理解范形的工作機制,并且介紹一些我們沒(méi)有提到的概念。特別的,我們要檢查Comparable接口和Enum類(lèi)(枚舉類(lèi)型的超類(lèi),后面一張講解)并且學(xué)習一些重要但是很少使用的范型特性,比如通配符下界。
在java1.5中,Comparable接口被修改為范型的。大多數的類(lèi)都實(shí)現了這個(gè)接口,考慮一下Integer:
public final class Integer extends Number implements Comparable<Integer>
原先的Comparable接口在類(lèi)型安全方面是有問(wèn)題的。兩個(gè)繼承了Comparable接口的對象可能不能相互比較。JDK5.0前,非范形的Comparable接口是非常有用但是不安全的,而現在的接口,捕獲了我們需要的信息:他告訴我們一個(gè)對象是可比較的,并且可以同什么比較。
現在,考慮一下comparable類(lèi)的子類(lèi)。Integer是final的,所以不能有子類(lèi),那么讓我們看看java.math.BigInteger:
public class BigInteger extends Number implements Comparable<BigInteger>
如果我們實(shí)現一個(gè)BiggerInteger類(lèi)是BigInteger的子類(lèi),他從父類(lèi)那里繼承了Comparable接口,但是注意繼承的是Comparable<Integer>而不是Comparable<BiggerInteger>。這意味著(zhù)BigInteger和BiggerInteger是可以相互比較的,這是非常好的。BiggerInteger可以重載compareTo(),但是不允許實(shí)現一個(gè)不同的參數化的Comparable。這就是說(shuō)BiggerInteger不能同時(shí)繼承BigInteger和實(shí)現Comparable<BiggerInteger>。
當你使用可比較的對象時(shí)(當寫(xiě)排序算法的時(shí)候)記住兩點(diǎn)。首先,使用原生類(lèi)型是不夠充分的:考慮到類(lèi)型安全,必須指明同什么比較。接下來(lái),類(lèi)型是不允許同自己比較的:有時(shí)候他會(huì )同他的祖先比較。為了具體說(shuō)明,考慮java.util.Collections.max():
這是一個(gè)冗長(cháng)而且復雜的方法標簽,我們來(lái)一步步考慮:
方法中包含一個(gè)類(lèi)型變量T,并且有復雜的綁定,稍后我們返回來(lái)討論。
方法的返回值類(lèi)型是T。
方法名是max()。
方法的參數是一個(gè)集合。元素的類(lèi)型指定為綁定的通配符。我們并不知道元素的確切類(lèi)型,但直到有一個(gè)上限T。所以我們知道元素的類(lèi)型要么為T(mén),要么是T的子類(lèi)。集合的任何元素都可以作為返回值使用。
這些是比較簡(jiǎn)單的,本章我們已經(jīng)看到了通配符上界,我們再來(lái)看看max()中的類(lèi)型變量聲明:
<T extends Comparable<? super T>>
要說(shuō)明的第一點(diǎn),T必須實(shí)現了Comparable接口。(范型的語(yǔ)法使用關(guān)鍵字extends來(lái)代表類(lèi)型綁定,不論是類(lèi)或接口)這是期望的,因為這個(gè)方法是找到集合中最大的元素。但是觀(guān)察這個(gè)參數化的Comparable接口,這是一個(gè)通配符,但是這個(gè)通過(guò)關(guān)鍵字super來(lái)綁定,而不是extends。這是下界綁定。? extends T是我們熟悉的上界綁定:這意味著(zhù)T或者其子類(lèi)。? super T比較少用:這意味著(zhù)T或者他的超類(lèi)。
總結一下,類(lèi)型變量聲明表明:“T是一個(gè)實(shí)現了Comparable接口或者他的父類(lèi)實(shí)現了該接口的類(lèi)型?!盋ollections.min()和Collections.binarySearch()有著(zhù)相同的聲明。
對其他的下界通配符(對于Comparable接口沒(méi)有作用)的例子,Collections中的addAll(),copy(),和fill()。觀(guān)察addAll()的聲明:
public static <T> boolean addAll(Collection<? super T> c, T... a)
這是一個(gè)varargs方法,接受任意數量的參數,并且傳遞給他們一個(gè)T[],命名為a。他將a中的所有元素都賦給集合c。集合的元素類(lèi)型雖然不知道,但是有一個(gè)下界:元素均為T(mén)或者T的超類(lèi)。不論類(lèi)型是什么,我們可以確定數組的元素都是類(lèi)型的實(shí)例,所以將數組的元素添加到集合中是合法的。
返回到我們先前討論的上界通配符,如果有一個(gè)集合的元素是上界通配符,那么都是只讀的??紤]List<? extends Serializable>。我們知道,所有的元素都是Serializable,所以像get()這樣的方法返回一個(gè)Serializable類(lèi)型的返回值。編譯器不允許我們調用add()這樣的方法,因為實(shí)際的元素類(lèi)型是不可知的。不能夠添加絕對的Serializable對象到list中,因為實(shí)現他們的類(lèi)可能不是正確的類(lèi)型。
既然上界統配符的結果是只讀的,所以你可能會(huì )期望下界通配符來(lái)實(shí)現只寫(xiě)的集合。實(shí)際并不是這樣,假設這里有一個(gè)List<? extends Integer>。元素的實(shí)際類(lèi)型是不知道的,但是可能性是Integer或者他的祖先類(lèi)Number和Object。無(wú)論實(shí)際類(lèi)型是什么,將Integer類(lèi)型(而不是Number和Object對象)的元素添加到list中是安全的。無(wú)論實(shí)際類(lèi)型是什么,list中所有元素都是Object對象的實(shí)例,所以list中像get()一樣的方法返回Object。
最后,讓我們把注意力放到j(luò )ava.lang.Enum類(lèi)。Enum是所有枚舉類(lèi)型的父類(lèi),它實(shí)現了Comparable接口,但是有一個(gè)讓人迷惑的范型聲明方法:
public class Enum<E extends Enum<E>> implements Comparable<E>, Serializable
第一眼,類(lèi)型變量E的聲明在一個(gè)循環(huán)中。再仔細的看一看:聲明真正說(shuō)明了,Enum必須是一個(gè)本身就是Enum類(lèi)型的類(lèi)型。這種表面上的循環(huán)是很顯然的,如果我們看到了implements子句。正如我們看到的,Comparable類(lèi)通常被定義為可以同自己比較的。而且他們的子類(lèi)也可以同他們的父類(lèi)比較。從另一個(gè)方面將,Enum實(shí)現了Comparable接口不是為了他本身,而是為了他的子類(lèi)E。