泛型方法
所谓泛型方法,就是带有类型参数的方法,它既可以定义在泛型类中(例如public void show(T aa),在使用泛型上没有任何特殊语法要求),也可以定义在普通类中(需要自定义参数类型,那么把泛型参数放在方法上就可以了,就是放在返回值类型之前,例如public <T> void show(T aa)),静态方法不能访问类的泛型,如果需要泛型只能在方法上使用泛型,例如
public static <T> void show(T aa)
具体应用案例
有一个接口IA,但是具体实现类不确定,具体实现类的创建交给配置文件
定义类或者方法时,不能确定所需要的参数类型
标准语法
依托于泛型类的写法
public class MyClass<T>{
public void pp(T id){}
public T abc(String name){}
//不能是静态方法,如果静态则必须声明,而且和类上的T无关
}
这种写法如果类已经确定则T的类型全部确定
不依靠泛型类,类有可能是没有泛型类
public class A{
public <T> 返回类型 方法名称(T t){}
如果T有约束则必须使用<T extends IA>,方法可以是静态的,也可以是非静态
}
特殊的通配符写法
public void pp(类型<? 约束> 参数名){}
约束可以使用extends和super两种
泛型类的继承
泛型类也是可以继承的,任何一个泛型类都可以作为父类或子类。不过泛型类与非泛型类在继承时
的主要区别在于:
- 泛型类的子类必须将泛型父类所需要的类型参数,沿着继承链向上传递。这与构造方法参数必须
- 沿着继承链向上传递的方式类似。
- 子类不是泛型类:需要给父类传递类型常量,如class AA extends A<String>,如果不设置则默认class Aa extends A<Object>
- 子类是泛型类:可以给父类传递类型常量,也可以传递类型变量。如class AA3<E> extends A<E>
如果父类中泛型有约束
public class A1<T extends Serializable> {}
public class B1<D extends Serializable> extends A1<D>{} //声明子类型时在子类上声明约束
可以写成public class B1<D extends Serializable> extends A1{},此时A1中T就是Object类型的
泛型的擦除
实际上,从虚拟机的角度看,不存在泛型概念。
泛型是运用在编译时期的技术:编译时编译器会按照<类型名>的类型对容器中的元素进行检查,检查不匹配,就编译失败。如果全部检查成功,则编译通过,但编译通过后产生的.class文件中还有<类型名>这个标识,即字节码的类文件中有泛型,但是运行时并没有泛型这就是泛型的擦除。
可以通过使用javap反编译查看字节码文件,可以看到其中包含泛型
一句话总结就是:在.java文件运用泛型技术时,编译器在文件编译通过后运行时自动擦除泛型标识。
abstract class A1<TF> {
private Class<TF> clz; // 就是T的具体类型
// 在构造器中获取T的具体类型
public A1() {
ParameterizedType c = (ParameterizedType) this.getClass().getGenericSuperclass(); // 获取到父类型的定义com.yan6.A1<T>
clz=(Class)(c.getActualTypeArguments()[0]); //获取真实的类型参数 T
}
由于泛型的擦除,运行时并没有泛型机制,同时也没有使用向下类型转换,那么为何运行时无异常?
这是由于泛型的补偿
List<String> list=new ArrayList<>();
for(String tmp:list) System.out.println(tmp.length()); //泛型的补偿编
译器在擦除泛型后,会自动将类型转换为原定义的泛型,这样就不必再做向下类型转换了。
for(Object tmp:list){
if(tmp instanceof String){
System.out.println(((String)tmp).length());
}
}
泛型的擦除和补偿这两个机制都是编译器内部自动完成的。
可以通过反射获取类型参数
泛型的局限性
- 不能使用基本类型
- 不能使用泛型类异常
- 不能使用泛型数组
- 不能实例化参数类型对象。例如T ob = new T();