1. 有界泛型
1.1 有界泛型的定义
当我们需要一个工具类只用于做数值计算,或者只用于做集合的遍历,方法只希望接收Number或者Collection,不希望接受其他类型,否则就得写一大堆instanceof的if判断。 为了解决这个问题,我们需要定义泛型的上边界,即方法所能接收的层级最高的类型。
在Java中,用extends关键字可以实现有界泛型的定义。格式为:<T extends UpperBoundClass>,其中UpperBoundClass为上边界类型。代码实例如下所示:
public static <T extends Number> void computeNumber(T number1, T number2) {
//compute
}
public static <T extends Collection> void iter(T collection) {
//iterate
}
代码清单1
在使用代码清单1的工具方法的时,可以这么使用:
//compile success
Integer number1 = 0;
BigDecimal number2 = new BigDecimal("2");
computeNumber(number1, number2);
iter(new ArrayList<>(Arrays.asList("1", "2", "a")));
iter(new HashSet<>(Arrays.asList("1", "2", "a")));
//compile fail: Wrong 1st argument type
computeNumber("string1", number2);
代码清单2
可见,只要定义了边界,泛型方法就只会接收符合条件的类型。而当传入不属于边界类型的子类时,将会出现编译时的参数类型错误。
1.2 对边界类型的访问
除了可以限制类型之外,有界范型的定义还允许直接访问边界类型,比如:
public static <T extends Collection> void iter(T collection) {
//iterate
if (!collection.isEmpty()) {
for (Object ele : collection) {
System.out.println(ele);
}
}
}
代码清单3
在代码清单3中,虽然方法参数collection的类型还未确定,依然可以访问边界类型的公有方法。
1.3 多重边界类型
Java支持范型的多重边界的定义,但有一定的限制:
- 支持多接口类型边界。
- 支持有且仅有一个类边界及其他接口边界。
- 不支持多个类边界。
原因很简单,Java不支持类的多继承关系,所以一个类不可能有多个无关系的父类。 而接口可以多继承接口,所以支持多个接口类型边界。代码示例如下所示:
public static <T extends Number & Cloneable> void computeCloneableNumber(T number) {
System.out.println(number.intValue());
}
public static <T extends Serializable & Cloneable> void visitCAndSObject(T object) {
System.out.println(object);
}
public static interface CAndS extends Cloneable, Serializable {
}
public static class CloneableInteger extends Number implements Cloneable {
private Integer data;
public CloneableInteger(Integer data) {
this.data = data;
}
public Integer getData() {
return data;
}
...
}
//调用
//compile success
computeCloneableNumber(new CloneableInteger(1));
visitCAndSObject(new CAndS(){});
//compile fail,提示Integer不能转成Cloneable
computeCloneableNumber(1);
代码清单4
多重边界的范型,编译器类型擦出过后,总是保留最左侧的边界类型作为实际类型,在代码清单4中,computeCloneableNumber和visitCAndSObject在字节码中的方法参数分别为Number和Serializable:
public static <T extends java.lang.Number & java.lang.Cloneable> void computeCloneableNumber(T);
descriptor: (Ljava/lang/Number;)V
public static <T extends java.io.Serializable & java.lang.Cloneable> void visitCAndSObject(T);
descriptor: (Ljava/io/Serializable;)V
附件
本博文所展示的源代码由此获得。
参考文献
[1] Josh Juneau.泛型:工作原理及其重要性[EB/OL].https://www.oracle.com/technetwork/cn/articles/java/juneau-generics-2255374-zhs.html,2019-07-02.
[2] The Java™ Tutorials - Generics[EB/OL].https://docs.oracle.com/javase/tutorial/java/generics/index.html,2019-07-02.
[3] The Java® Virtual Machine Specification[EB/OL].https://docs.oracle.com/javase/specs/jvms/se10/html/jvms-4.html#jvms-4.3,2019-07-04.