[Java] 淺談泛型的意義與不足
示意 - 泛型程式碼
// 類的泛型
class Two<A,B> {
public A first;
public B second;
public Two(A a, B b) {
first = a;
second = b;
}
}
class Test {
// 方法的泛型
static <T> T get(T x) { return x; }
}
泛型是什麼
上面程式碼中,Two這個類使用了泛型(在類名的後面指定了<A,B>
),則在例項一個Two物件時可代入兩個型別A和B。一般我們可以稱Two為一個容器。使用如下:
Two<String, Integer> a = new Two<>("Test", 2);
a.first = "modify";
System.out.print(a.first);
泛型方法的宣告,見Test類中的get方法,會根據代入的型別確定T:
System.out.println(Test.get(3)+Test.get("test..."));
有什麼用
就可以方便的代入指定型別,達到模板複用的效果。
上方對Two類的使用中,雖然在類裡first域是一個A型別,無意義。但在我呼叫a.first時,已經被IDE與編譯器視為String型別了。
效果:只要你指明瞭型別,編譯器會幫你處理好一切。
另外:
泛型類與泛型方法完全可以分開考慮,泛型類可以沒有泛型方法,泛型方法可以不在泛型類中(可參考考慮Test類)
擦除與邊界
如前面所見的Two型別,first與second貌似已經在新建中變成了String與Integer型別,在取出與存入時都可以將其視為對應型別。但實際上不是這樣的。
first與second在Two類中,是沒有其它屬性的,它們只是一個Object。
那為什麼在讀寫時我們可以使用對應的型別呢?
以下需要提兩個概念:
擦除:可以在類Two中嘗試打入a.
會發現沒有什麼功能。因為編譯器在類的內部處理中,將first與second的屬性擦除了,只把它們視為Object。
邊界:編譯器唯一對泛型處理的地方是,當我們寫入如a.second = 3
時,編譯器將3轉為一個Object
a.second
;當我們讀取此域如System.out.println(a.second)
時,編譯器將a.second
讀出後轉為了Integer型別。即是說,一切泛型的作用,都只發生在它讀取的時候。
如何保持它在類中功能
這涉及到extends方法,見如下程式碼,B是A的實現類或派生類:
interface A { int get(); }
class B implements A {
public int get() { return 1; }
}
class Test1<T extends A> {
T x;
Test1(T x) { this.x = x; }
void test() { System.out.println(x.get()); }
}
public class Demo
{
@Test
public void test() {
Test1<B> t = new Test1<>(new B());
t.test();
}
}
使用此類寫法時,內部直接將x看作A型別了。那麼這樣有何意義呢?
我認為唯一的意義還是在於邊界處,即讀寫時,會將此成員轉為B型別。
泛型的不足
以下不足可以在面試中吹吹牛皮用:
擦除的問題
由於擦除,Java不能獲取泛型的具體資訊。這就導致以下後果:
泛型在C++中對應的功能是模板類,在模板中可以使用T x的方法,比如x.f(),然後在例項此模板時編譯器自動判斷T為是否帶有f()方法。而Java將T的一切都擦除了,導致T實際上幾乎沒有功能。雖然可以使用來補充擦除的邊界,但這樣還不如直接把T當成基類HasF來使用(即將類中的T去掉,將所有用到T的型別換成HasF),也能達到一樣的效果。因此在平時直接使用T這樣的泛型時,必須提醒自己,這是一個Object,泛型的效果僅在發生在代入與傳出值時。
不能使用一些型別
Test<T>
中的T不能代入int\float等Java預設基本型別(雖然已有對應的Integer等類了)
Test<T>
中的T不能是Throwable的派生類,即是代入的T不能是會丟擲異常的類。
不能同時擁有一個泛型介面的多種實現
class C<T> {}
class A implements C<Integer> {}
// error
class B extends A implements C<String> {}
這樣編譯是無法通過的,B繼承A時已經擁有了C<Integer>
就無法使用的。這種限定的好壞見仁見智吧。
不能過載
如上述Two<A,B>
中如果實現一個函式void f(A x)就不能再寫void f(B x)了,這不符合Java過載的機制。原因其實很好理解,因為A與B在類的內部統一被當作Object了。