泛型类型的继承规则
首先,我们来看一个类和它的子类,比如 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<?extendsFruit>
的子类型。
我们接下来来考虑另外一个问题,使用通配符会通过 Pair<?extendsFruit>
的引用破坏 Pair<Apple>
吗?
Pair<Apple> applePair = new Pair<>(apple1, apple2);Pair<? extends Fruit> sonFruitPair = applePair;sonFruitPair.setFirst(banana);
这样可能会引起破坏,但是当我们调用 setFirst
的时候,如果调用的不是 Fruit
的子类 Apple
类的对象,而是其他 Fruit
子类的对象,就会出错。 我们来看一下 Pair<?extendsFruit>
的方法:
? 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接口,描述类型变量(如
TextendsComparable<?superT>
) 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
打印结果: