Java基礎系列(三十七):泛型繼承,萬用字元,泛型反射
泛型型別的繼承規則
首先,我們來看一個類和它的子類,比如 Fruit
和 Apple
。但是Pair<Apple>
是Pair<Fruit>
的一個子類麼?並不是。比如下面的這段程式碼就會編譯失敗:
Apple[] apples = ...;
Pair<Fruit> answer = ArrayAlg.minmax(apples); //ERROR
我們需要記住:無論S和T有什麼聯絡,Pair<S>
與Pair<T>
沒有什麼聯絡。
這裡需要注意泛型和Java陣列之間的區別,可以將一個Apple[]
陣列賦給一個型別為Fruit[]
Apple[] apples = ...;
Fruit[] fruit = apples;
然而,陣列帶有特別的保護,如果試圖將一個超類儲存到一個子類陣列中,虛擬機器會丟擲ArrayStoreException
異常。
永遠可以將引數化型別轉換為一個原始型別,比如,Pair<Fruit>
是原始型別Pair
的一個子型別。在與遺留程式碼對接的時候,這個轉換非常必要。
泛型類可以擴充套件或實現其他的泛型類,比如,ArrayList<T>
類實現了List<T>
介面,這意味著,一個ArrayList<Apple>
可以轉換為一個List<Apple>
ArrayList<Apple>
不是一個ArrayList<Fruit>
或List<Fruit>
。
萬用字元型別
萬用字元型別中,允許型別引數變化。比如,萬用字元型別:
Pair<? extends Fruit>
表示任何泛型型別,它的型別引數是Fruit的子類,如Pair<Apple>
,單不會是Pair<String>
。
假如現在我們需要編寫一個方法去列印一些東西:
public static void printBuddies(Pair<Fruit> p) {
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
}
正如前面所講到的,不能將Pair<Apple>
傳遞給這個方法,這一點很受限制。解決的方案很簡單,使用萬用字元型別:
public static void printBuddies(Pair< ? extends Fruit> p)
Pair<Apple>
是Pair<? extends Fruit>
的子型別。
我們接下來來考慮另外一個問題,使用萬用字元會通過Pair<? extends Fruit>
的引用破壞Pair<Apple>
嗎?
Pair<Apple> applePair = new Pair<>(apple1, apple2);
Pair<? extends Fruit> sonFruitPair = applePair;
sonFruitPair.setFirst(banana);
這樣可能會引起破壞,但是當我們呼叫setFirst
的時候,如果呼叫的不是Fruit
的子類Apple
類的物件,而是其他Fruit
子類的物件,就會出錯。
我們來看一下Pair<? extends Fruit>
的方法:
? extends Fruit getFirst();
void setFirst(? extends Fruit);
這樣就會看的很明顯,因為如果我們去呼叫setFirst()
方法,編譯器之可以知道是某個Fruit
的子型別,而不能確定具體是什麼型別,它拒絕傳遞任何特定的型別,因為 ? 不能用來匹配。
但是使用getFirst
就不存在這個問題,因為我們無需care它獲取到的型別是什麼,但一定是Fruit
的子類。
萬用字元限定與型別變數限定非常相似,但是萬用字元型別還有一個附加的能力,即可以指定一個超型別限定:
? super Apple
這個萬用字元限制為Apple
的所有父類,為什麼要這麼做呢?帶有超型別限定的萬用字元的行為與子型別限定的萬用字元行為完全相反,可以為方法提供引數,但是卻不能獲取具體的值,即訪問器是不安全的,而更改器方法是安全的:
? extends Fruit getFirst();
void setFirst(? extends Fruit);
編譯器無法知道setFirst
方法的具體型別,因此呼叫這個方法時不能接收型別為Fruit
或Object
的引數。只能傳遞Apple
型別的物件,或者某個子型別(Banana
)物件。而且,如果呼叫getFirt
,不能保證返回物件的型別。
總結一下,帶有超型別限定的萬用字元可以想泛型物件寫入,帶有子型別限定的萬用字元可以從泛型物件讀取。
還可以使用無限定的萬用字元,例如,Pair<?>
。初看起來,這好像與原始的Pair型別一樣,實際上,有很大的不同。型別Pair<?>
有以下方法:
? getFirst();
void setFirst(?);
getFirst
的返回值只能返回一個Object
。setFirst
方法甚至不能被呼叫,甚至不能用Object
呼叫。Pair<?>
和Pair
的本質的不同在於:可以用任意Object
物件呼叫原始Pair
類的setObject
方法。
可以呼叫
setFirst(null)
為什麼要使用這樣脆弱的型別?
//判斷pair是否包含一個null引用
public static boolean hasNulls(Pair<?> p) {
return p.getFirst() == null || p.getSecond() == null;
}
通過將hasNulls
轉換為泛型方法,可以避免使用萬用字元型別:
public static <T> boolean hasNulls(Pair<T> p)
但是,帶有萬用字元的版本可讀性更強。
那麼萬用字元該怎麼去捕獲呢?
public static void swap(Pair<?> p)
萬用字元不是型別變數,所以,我們在編寫程式碼的時候不能使用"?"
作為一種型別,也就是說,下面的程式碼是錯誤的:
? t = p.getFirst();
這裡有一個問題,因為在交換的時候必須臨時儲存第一個元素,我們這裡可以寫一個輔助方法swapHelper
:
public static <T> void swapHelper(Pair<T> p){
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
注意,swapHelper
是一個泛型方法,而swap
不是,它具有固定的Pair<?>
型別的引數。現在我們可以由swap
呼叫swapHelper
:
public static void swap(Pair<?> p) {
swapHelper(p);
}
在這種情況下,swapHelper
方法的引數T
捕獲萬用字元,它不知道是哪種型別的萬用字元,但是,這是一個明確的型別,並且<T>swapHelper
的定義只有在T
指出型別時才有明確的含義。
萬用字元捕獲只有在有許多限制的情況下才是合法的。編譯器必須能夠確信萬用字元表達的是單個,確定的型別。例如,ArrayList<Pair<T>>
中的T
永遠不能捕獲ArrayList<Pair<?>>
中的萬用字元。陣列列表可以儲存兩個Pair<?>
,分別針對?
的不同型別。
反射與泛型
反射允許我們在執行時分析任意的物件,但是如果物件是泛型類的例項,關於泛型型別引數則得不到太多資訊,因為它們會被擦除。
為了表達泛型型別宣告,使用java.lang.reflect
包中提供的介面Type
,這個介面包含下列子型別:
Class類,描述具體型別
TypeVariable介面,描述型別變數(如T extends Comparable<? super T>
)
WildcardType介面,描述萬用字元
ParameterizedType介面,描述泛型類或介面型別
GenericArrayType介面,描述泛型介面
下面是一個使用泛型反射API打印出給定類的有關內容的程式:
public class GenericReflectionTest
{
public static void main(String[] args)
{
String name;
if (args.length > 0) name = args[0];
else
{
try (Scanner in = new Scanner(System.in))
{
System.out.println("Enter class name (e.g. java.util.Collections): ");
name = in.next();
}
}
try
{
// print generic info for class and public methods
Class<?> cl = Class.forName(name);
printClass(cl);
for (Method m : cl.getDeclaredMethods())
printMethod(m);
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
public static void printClass(Class<?> cl)
{
System.out.print(cl);
printTypes(cl.getTypeParameters(), "<", ", ", ">", true);
Type sc = cl.getGenericSuperclass();
if (sc != null)
{
System.out.print(" extends ");
printType(sc, false);
}
printTypes(cl.getGenericInterfaces(), " implements ", ", ", "", false);
System.out.println();
}
public static void printMethod(Method m)
{
String name = m.getName();
System.out.print(Modifier.toString(m.getModifiers()));
System.out.print(" ");
printTypes(m.getTypeParameters(), "<", ", ", "> ", true);
printType(m.getGenericReturnType(), false);
System.out.print(" ");
System.out.print(name);
System.out.print("(");
printTypes(m.getGenericParameterTypes(), "", ", ", "", false);
System.out.println(")");
}
public static void printTypes(Type[] types, String pre, String sep, String suf,
boolean isDefinition)
{
if (pre.equals(" extends ") && Arrays.equals(types, new Type[] { Object.class })) return;
if (types.length > 0) System.out.print(pre);
for (int i = 0; i < types.length; i++)
{
if (i > 0) System.out.print(sep);
printType(types[i], isDefinition);
}
if (types.length > 0) System.out.print(suf);
}
public static void printType(Type type, boolean isDefinition)
{
if (type instanceof Class)
{
Class<?> t = (Class<?>) type;
System.out.print(t.getName());
}
else if (type instanceof TypeVariable)
{
TypeVariable<?> t = (TypeVariable<?>) type;
System.out.print(t.getName());
if (isDefinition)
printTypes(t.getBounds(), " extends ", " & ", "", false);
}
else if (type instanceof WildcardType)
{
WildcardType t = (WildcardType) type;
System.out.print("?");
printTypes(t.getUpperBounds(), " extends ", " & ", "", false);
printTypes(t.getLowerBounds(), " super ", " & ", "", false);
}
else if (type instanceof ParameterizedType)
{
ParameterizedType t = (ParameterizedType) type;
Type owner = t.getOwnerType();
if (owner != null)
{
printType(owner, false);
System.out.print(".");
}
printType(t.getRawType(), false);
printTypes(t.getActualTypeArguments(), "<", ", ", ">", false);
}
else if (type instanceof GenericArrayType)
{
GenericArrayType t = (GenericArrayType) type;
System.out.print("");
printType(t.getGenericComponentType(), isDefinition);
System.out.print("[]");
}
}
}
比如,我們輸入java.util.Collections
列印結果:
具體的API請查閱API文件