Java泛型如何深入解析和应用?
摘要:本文将带你详细解析 Java 泛型,了解泛型的原理、常见的使用方法以及泛型的局限性,让你对泛型有更深入的了解。
泛型的定义
泛型类的定义
下面定义了一个泛型类 Pair,它有一个泛型参数 T。
public class Pair<T> {
private T start;
private T end;
}
实际使用的时候就可以给这个 T 指定任何实际的类型,比如下面所示,就指定了实际类型为 LocalDate,泛型给了我们一个错觉就是通过个这个模板类 Pair<T>,我们可以在实际使用的时候动态的派生出各种实际的类型,比如这里的 Pair<LocalDate> 类。
Pair<LocalDate> period = new Pair<>();
泛型类的继承
子类是一个泛型类的定义方法如下:
public class Interval<T> extend Pair<T> {}
这里的 Interval<T> 类是一个泛型类,也可以像上面使用 Pair<T> 类一样给它指定实际的类型。
子类是一个具体类的定义方法如下:
public class DateInterval extends Pair<LocalDate> {}
这里的 DateInterval 类就是一个具体的类,而不再是一个泛型类了。这里的语义是 DateInteral 类继承了 Pair<LocalDate> 类,这里的 Pair<LocalDate> 类也是一个具体类。但是由于 Java 的泛型实现机制,这里会带来多态上的一个问题,见下面的分析。
而像下面的这种定义具体类的写法是错误的:
public class DateInterval<LocalDate> extends Pair<LocalDate> {}
泛型方法的定义
泛型方法定义时,类型变量放在修饰符的后面,返回值的前面。泛型方法既可以泛型类中定义,在普通类中定义。
public static <T> T genericMethod(T a) {}
这里顺便记录一下,因为是使用擦除来实现的泛型,因此字节码中的方法的签名是不会包含泛型信息的。对于泛型方法会多生成一个 Signature 的属性,用于记录方法带泛型信息的签名,反编译器也可以根据这个信息将泛型方法还原回来。
构造函数泛型
下面的代码定义了一个泛型类 ConstructorGeneric,它的泛型参数是 T,这个类的构造函数也是泛型的,它有一个泛型参数 X。
class ConstructorGeneric<T> {
public <X> ConstructorGeneric(X a) {}
}
创建该对象的代码如下:
ConstructorGeneric<Number> t = new <String>ConstructorGeneric<Number>("123");
这里 new 后面的 String 是传给构造器的泛型 X 的,即 X 的实际类型为 String;类的范型参数是由 Number 传递的,即 T 的实际类型是 Number。这里两个都是省略,写在这里是为了显示区分出两个参数传递的位置。
类型变量的限定
带单个上界限定:
下面的代码定义了一个 NatualNumber 类,它的泛型参数 T 限制为 Integer 或者 Integer 的子类。
public class NaturalNumber<T extends Integer> {
private T n;
public NaturalNumber(T n) { this.n = n; }
public boolean isEven() {
return n.intValue() % 2 == 0;
}
}
调用代码如下:
// 正常
NaturalNumber<Integer> natural1 = new NaturalNumber<>(1);
// 无法编译,因为这里和泛型类定义的上界不符合
NaturalNumber<Double> natualral2 = new NaturalNumber<>(1.0);
带多个上界的限定:
多个上界之间使用 & 符号进行分隔,如果多个限定中有类,则类需要排在接口后面(因为 Java 不支持多继承,所以不存在有多个限定的类的情况)。
