泛型类型的继承规则
首先,我们来看一个类和它的子类,比如 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
打印结果:
原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知