用途
实现多态。泛型在实际的应用中,提高了代码抽象程度,把复杂而又相似的逻辑统一收束,无论在减少代码量的方面,还是在后续的维护与重构的方面,都能起到极大的作用。
这是很抽象而且假大空的描述了,把它具体化,变成例子。简单点的,List里面可以放String,但你放了String就不应该放Number,泛型的机制保证了这些部分。复杂点的,一个事件响应器handler抽象类,应该有不同的具体的类的实现,运用泛型能保证这些不同的具体的实现有相似的行为,都能达到事件处理的目的,但达成的方式有所不同。
实现机制
泛型擦除和强制类型转换。
java的泛型是被实现的特性,编译生成的字节码文件中是不含泛型中的类型信息的,jvm在运行的时候是没有泛型的,泛型只存在于源码中,在编译后就会消失,这个过程就是泛型的擦除。
大部分泛型的奇妙特性,都可以从泛型擦除这个底层的机制来找原因。
泛型类型
结构
它们和class都是Type的子接口,共同属于java.lang.reflect包,自jdk1.5起加入。
同时,Class<T>类也是Type的子类。
例子
private class TypeTest<T> {
private T[] ts;
public void wildCardTest(List<? extends T> list) {
}
public TypeTest(T[] tss) {
this.ts = tss;
}
}
在这个例子中,一个实例化的TypeTest<T> ,比如TypeTest<String>,就是一个ParameterizedType
TypeTest<T>中的T,就是TypeVariable
List<? extends T> list中的? extends T 就是WildcardType
T[] ts为GenericArrayType
他们有很多实用的api,可以在反射的时候用,jdk里注释写的很清楚,这里稍稍看一看。
package java.lang.reflect;
public interface GenericArrayType extends Type {
// 返回泛型数组中的泛型类型
Type getGenericComponentType();
}
package java.lang.reflect;
public interface ParameterizedType extends Type {
// 返回 ParameterizedType的 TypeVariable,如
// List<ArrayList> a1;//返回 ArrayList,Class类型
// List<ArrayList<String>> a2;返回 ArrayList<String>, ParameterizedType类型
Type[] getActualTypeArguments();
// 返回 rawType
// List<ArrayList> a1;//返回List
Type getRawType();
// 内部类返回外部的 rawType
Type getOwnerType();
}
package java.lang.reflect;
public interface TypeVariable<D extends GenericDeclaration>
extends Type,AnnotatedElement {
// 返回表示此 TypeVariable上限的Type对象数组。
// 如果没有明确声明上限,则上限为Object。
Type[] getBounds();
// 返回表示为此 TypeVariable声明的 GenericDeclaration 对象。
D getGenericDeclaration();
// 返回 TypeVariable在源码中的名字,如T,K,V
String getName();
// 返回一个 AnnotatedType 对象数组
// 这些对象表示使用类型来表示由此TypeVariable 表示的类型参数的上限。
// 数组中对象的顺序对应于类型参数声明中边界的顺序。
AnnotatedType[] getAnnotatedBounds();
}
package java.lang.reflect;
public interface WildcardType extends Type {
// 通配符上界
Type[] getUpperBounds();
// 通配符下界
Type[] getLowerBounds();
// one or many? Up to language spec; currently only one, but this API
// allows for generalization.
}
遇到的问题
泛型机制会有一些机制,在这里稍微做点总结。
public class Fruit{
…………
}
public class Melon extends Fruit{
…………
}
List<Fruit> fruits=new ArrayList<>();
List<Melon> melons=new ArrayList<>();
if(fruits.getClass()=melon.getClass()){} // true
if (fruits instanceof List){} // true
if (fruits instanceof List<String>){} // 无法通过编译
List<Fruit>[] qwe=new List[2]; // 合法
List<Fruit>[] table=(List<Fruit>[]) new List<?>[10]; // 合法
List<Fruit>[] qwe=new List<Fruit>[2]; // 无法通过编译
运行时无法保留泛型的声明,这是因为被擦除了类型,只能保留原始类型,这个特性在反射的实际运用中,会带来很多麻烦。
private class TypeTest<T> {
public TypeTest(T[] tss) {
T t= new T(); // 无法通过编译
T t= T.class.newInstance(); // 无法通过编译
}
}
不能实例化TypeVariable。
public static <T> Melon<T> getMelon(Class<T> clazz){
try {
return new Melon<>(clazz.newInstance(),clazz.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
Melon melon=Melon.getMelon(Melon.class);
需要通过反射的方式,间接的实例化对象。
通配符类型
Java中泛型是不变的,而数组是协变的.
不变,协变,逆变的定义
逆变与协变用来描述类型转换(type transformation)后的继承关系
数组是协变的,导致数组能够继承子元素的类型关系 :Number[] arr = new Integer[2];
-> OK
泛型是不变的,即使它的类型参数存在继承关系,但是整个泛型之间没有继承关系 :ArrayList<Number> list = new ArrayList<Integer>();
-> Error
带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取
下面的这个经典的复制可以配合理解这个问题
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
...
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
// dest 接收数据, src 提供数据
dest.set(i, src.get(i));
}
...
}
reference
文档信息
- 本文作者:nyaaar
- 本文链接:https://nyaaarlathotep.github.io/2021/09/20/java%E6%B3%9B%E5%9E%8B%E4%B8%80%E7%82%B9%E7%90%86%E8%A7%A3/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)