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 不支持多继承,所以不存在有多个限定的类的情况)。
阅读全文