泛型
以下内容均为java语言环境下的情况
泛型的分类:
-
泛型接口:interface GenericInterface<T> {}
-
泛型类:class GenericClass<T> implements GenericInterface<T> {}
- 类(接口)的泛型定义位置紧跟在类(接口)之后,可替代该类(接口)定义内部的任意类型;
- 该类(接口)被声明时,确定泛型参数;
-
泛型方法:public <K, E extends Exception> K genericMethod(K param) throws E {}
-
方法的泛型定义位置在修饰符之后,返回值之前,可替代该方法中使用的任意类型;
-
方法被调用时确定泛型参数;一般情况下是通过方法参数确定的泛型参数;
-
无参的泛型方法使用如下语法指定泛型参数:
// 声明泛型方法 public static <K extends Number>K getNum(){ return null; } // 在方法名之前确定泛型参数 Integer integer = GenericTest.<Integer>getNum();
-
有界泛型
- ? 通配符类型,指代某一个任意类型,但不是Object
- T extends UpperBound 泛型T的上界,可用于定义及声明处
在方法、接口、类的定义时需要使用泛型参数名(例如K、T),在声明位置使用时需要使用通配符? - T super LowerBound 泛型T的下界,泛型下界只能用于声明处,表示泛型参数一定是指定类或其父类。
泛型上界只能用于读取T及其子类,不能写入,泛型下界只能写入T及其子类,不能读
泛型通配符?示例
package generic;
class Parent {}
class Sub1 extends Parent {}
class Sub2 extends Parent {}
// 无界泛型
public class WildcardSample<T> {
T obj;
void test() {
WildcardSample<Parent> sample1 = new WildcardSample<>();
// 编译错误
// Required type: WildcardSample<Parent>
// Provided: WildcardSample<Sub1>
WildcardSample<Parent> sample2 = new WildcardSample<Sub1>();
WildcardSample<?> sample3 = new WildcardSample<Parent>();
WildcardSample<?> sample4 = new WildcardSample<Sub1>();
WildcardSample<?> sample5 = new WildcardSample<Sub2>();
sample1.obj = new Sub1();
// 编译错误:
// Required type: capture of ?
// Provided: Sub1
sample3.obj = new Sub1();
}
}
上界泛型示例:
package generic;
public class ExtendSample<T extends Parent> {
T obj;
<K extends Sub1> T extendMethod(K param) {
return this.obj;
}
}
class Generic {
public static void main(String[] args) {
// 泛型上界
ExtendSample<Parent> sample1 = new ExtendSample<>();
ExtendSample<Sub1> sample2 = new ExtendSample<Sub1>();
ExtendSample<? extends Parent> sample3 = new ExtendSample<Sub1>();
ExtendSample<? extends Sub1> sample4;
// 编译错误
// Required type: ExtendSample<? extends Sub1>
// Provided: ExtendSample<Sub2>
sample4 = new ExtendSample<Sub2>();
// 编译错误:声明的范围不在ExtendSample定义指定的范围内
// Type parameter '? extends Number' is not within its bound; should extend 'generic.Parent'
ExtendSample<? extends Number> sample5;
sample1.obj = new Sub1();
// 编译错误:上界泛型只能读,不能写
// Required type: capture of ? extends Parent
// Provided: Parent
sample3.obj = new Parent();
// 编译通过,使用上界Parent可以读出数据
Parent obj = sample3.obj;
}
}
下界泛型示例:
package generic;
public class SuperSample<T> {
T obj;
}
class GenericSuper {
public static void main(String[] args) {
// 泛型下界
SuperSample<? super Parent> sample1 = new SuperSample<Parent>();
// 编译错误:只能存放Parent或它的父类
SuperSample<? super Parent> sample2 = new SuperSample<Sub1>();
SuperSample<? super Sub1> sample3 = new SuperSample<Parent>();
sample1.obj = new Sub1();
sample1.obj = new Sub2();
sample1.obj = new Parent();
sample3.obj = new Sub1();
// 编译错误
sample3.obj = new Sub2();
// 编译错误:下界泛型只能写入声明的下界类型或其子类
sample3.obj = new Parent();
// 下界泛型只能写入,不能读,只能是Object类型接收数据
Object obj = sample3.obj;
}
}
复杂泛型
- 多个泛型参数定义由,分隔,例如<T, K>
- 同一个泛型参数如果有多个上界,那么各个上界用符号&连接(在反射时该参数按照第一个上界处理,使用反射获取该方法时需要方法名和参数类型,此时参数类型只能使用第一个上界的类型才能获取到)
- 多个上界类型里最多只能有一个类,其他必须为接口,如果有类则必须放在第一位。
数组和泛型容器
- 数组具有协变性,而泛型具有无关性
- 数组是具象化的,而泛型不是
若类A是类B的子类,则记作A≤B,设有变换f(),则有以下定律:
- 当A≤B时,有f(A)≤f(B),则f()具有协变性。
- 当A≤B时,有f(B)≤f(A),则f()具有逆变性。
- 如果以上两者都不成立,则变换f具有无关性。
例如:
// 编译通过
Object[] array = new String[10];
// 编译不通过
ArrayList<Object> list1 = new ArrayList<String>();
// 编译不通过
ArrayList<String> list2 = new ArrayList<Object>();
// String ≤ Object
// 数组的变换f(A) = A[]即:
// f(Object) = Object[] f(String) = String[]
// f(String) ≤ f(Object)成立,其具有协变性
// ArrayList的变换同理
// f(String) = ArrayList<String> f(Object) = ArrayList<Object>
// f(String) ≥ f(Object) 与 f(String) ≤ f(Object)均不成立,故其具有无关性
// 编译通过,但运行时抛出异常:java.lang.ArrayStoreException
array[0] = 1;
// 编译不通过:java中数组和泛型是不能混合使用的,数组要求类型是具象化的,而泛型恰好不是
// Generic array creation
List<String>[] genericListArray = new ArrayList<String>[10];
具象化:完全在运行时可用的类型被称为具象化类型(refiable type),有些类型会在编译过程中被擦除而无法在运行时可用
具象化包括:
- 非泛型类声明、接口类型声明
- 所有泛型参数类型为无界通配符(仅用?修饰)的泛型参数类
- 原始类型
- 基本数据类型
- 其元素类型为具象化类型的数组
- 嵌套类(内部类、匿名内部类等),并且嵌套过程中的每个类都是具象化的
泛型擦除
编译器处理带泛型定义的类、接口、方法时,会在字节码指令集中抹去全部泛型类型信息,泛型被擦除后字节码里只保留泛型的原始类型(一般对应的是泛型的定义上界,例如:<T>对应的原始类型是Object,<T extends String>对应是原始类型是String)。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,然后在必要的时候添加类型检查和类型转换的方法。
泛型的类型检查是针对引用的,而不是变量实际指向的对象
泛型擦除带来的问题:
- 泛型类型变量不能是基本数据类型
- 类型的丢失:泛型对象使用instanceof进行类型判断时不能用具体类型了,只能使用通配符?(list instanceof ArrayList<?>)
- catch中不能使用泛型异常类,也不能在catch中使用泛型变量T,由于泛型擦除会导致T被擦除为Throwable,捕获原则是先捕获子类类型异常,再捕获父类类型异常,违背了异常捕获原则,这种写法不被允许
- 泛型类的静态方法和属性不能使用泛型
编译器保留的泛型信息:
- 泛型接口、类、方法定义上的所有泛型
- 成员变量声明处的泛型
字节码中指令集之外的地方会保留部分泛型信息,只有局部代码块里的泛型被擦除了
反射获取泛型信息
示例代码

浙公网安备 33010602011771号