泛型介绍
我们都知道一个数据类型可以定义很多变量,例如:int a,b,c,d;那有没有一种类型可以定义或代表很多种数据类型呢?这就是泛型出现的原因。泛型简单来说就是数据类型参数化。
java中泛型是在jdk1.5出现的,之所以能出现还要得益于Object对象,因为java中泛型属于伪泛型,采用泛型擦除机制,最终泛型对象都是被Object对象引用。
既然说最终都是Object对象,那为什么还要出现泛型,这不是对此一举吗?因为泛型带来的好处是编译时期检查,可以防止"猫中出现狗"的现象。
jdk中跟泛型有关的接口
type
type是所有类型的父接口,只有一个方法
public interface Type {
//返回描述这个类型的字符串
String getTypeName() {
return toString();
}
}
它有四个子接口和一个实现类,实现类是Class,子接口是:ParameterizedType,TypeVariable,WildcardType,GenericArrayType。Class类就不详细介绍,下面介绍其四个子接口。
ParameterizedType参数化的类型
ParameterizedType表示的是泛型被指定为具体数据类型的类型,例如: Map<Cat,Dog>,List<Cat>等等
ParameterizedType中的方法:
public interface ParameterizedType extends Type {
//获取实际的类型参数,例如List<Cat>中的Cat的Class对象
Type[] getActualTypeArguments();
//获取原生类型,例如List<Cat>中的List的Class对象
Type getRawType();
//获取拥有者类型,指的是这个类或者接口是定义在另一个类或者接口的内部的
//例如Map中的Entry接口,获取到的类型就是Map的Class对象,如果是顶层类则返回的是null
Type getOwnerType();
}
TypeVariable类型变量
TypeVariable表示的是原始的泛型信息
TypeVariable中的方法:
public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
//获取泛型类型的上边界类型,为什么是数组,因为可以通过 & 指定多个上边界
//例如List<T extends Number & Serializable>
Type[] getBounds();
//获取泛型声明,意思是泛型声明的地方,java中有三个地方可以声明泛型
//1、类上,例如:List<T>,此方法返回的就是 List的Class对象
//2、构造器上,一般用不到,没有多大意义,例如:public <T> Cat(T t){},此方法返回的就是 Cat的Constructor对象
//3、方法上,例如:public <T> void say(T t){},此方法返回的就是Method对象
D getGenericDeclaration();
//返回泛型的名称,例如List<T>中的T
String getName();
//这个方法是jdk1.8出来的,用于获取上边界的注解类型,注解可以声明在泛型的边界上
//举个例子,有注解@Beauty, List<T extends @Beauty Number>
AnnotatedType[] getAnnotatedBounds();
}
WildcardType 通配符类型
通配符一般用于方法参数的位置, 例如 public void process(List<? extend number>){}
WildcardType 中的方法:
public interface WildcardType extends Type {
//获取上边界,可以通过 & 指定多个上边界
Type[] getUpperBounds();
//获取下边界,可以通过 & 指定多个下边界
Type[] getLowerBounds();
}
解析泛型
泛型解析主要是框架或者是类库的开发者而言的,对于我们这些普通的开发者来说基本用不到泛型解析。为什么要对泛型进行解析呢?因为框架开发者并不知道我们所使用的类是什么,又要使框架能正确的处理我们写的类。
举个例子,
public interface UserMapper{
@Select("select * from tb_user where username = "#{name}"")
List<User> selectUserByName(@Param("name")String name)
}
没有使用xml指定返回值类型是User对象,那么Mybatis如何知道把结果集封装到User对象中的呢?
//通用解析方法,可以根据实际情况做修改
public List<Class<?>> getActualGenericClass(Type type) {
//不存在泛型
if (type instanceof Class) {
return Collections.singletonList((Class<?>) type);
}
//存在泛型
if (type instanceof ParameterizedType) {
List<Class<?>> list = new ArrayList<>();
ParameterizedType parameterizedType = (ParameterizedType) type;
//获取泛型实际的参数
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
//如果有一个未指定泛型类型则抛出异常,例如:Map<String,T>,此时T未指定具体类型
if (!(actualTypeArgument instanceof Class)) {
throw new IllegalArgumentException();
}
list.add((Class<?>) actualTypeArgument);
}
return list;
}
//如果不是ParameterizedType,则说返回值是类型变量,
// 例如,public <T> T get(){} 中的返回值T
// 抛出异常
throw new IllegalArgumentException();
}
1、解析方法返回值及字段的泛型
//解析字段中的泛型和解析方法返回值的泛型是基本一样的,把Method换成Field,
//method.getGenericReturnType()换成field.getGenericType()即可
//假设拿到了UserMapper中的selectUserByName方法
Method method = getMethod(clazz);
//获取返回值的泛型类型
Type returnType = method.getGenericType();
//list中只有一个元素,为User的Class对象
List<Class<?>> list = getActualGenericClass(returnType);
2、解析类上的泛型
一般解析类上的泛型是借助超类实现的,比如通用Mapper,要继承Mapper接口,并指定泛型类型,还有比如fastjson中的TypeReference对象,传递此参数时要指定其实现子类或者匿名类,并指定泛型类型。
按照通用Mapper方式进行解析:
public interface UserMapper extends Mapper<User> {
}
Class<?> clazz = UserMapper.class;
Type type = clazz.getGenericSuperclass();
//list中只有一个元素,为User的Class对象
List<Class<?>> list = getActualGenericClass(type);
fastjson中,我们想要把json字符转解析成List<User>,可以创建一个TypeReference的子类并指定泛型的类型为List<User>,泛型类型会被解析成ParameteredType,getRawType()方法返回的是List的Class对象,getActualTypeArguments()方法返回的数组只有一个,为User的Class对象。User对象是我们定义的,但是框架通过泛型解析拿到了User对象的Class信息,通过反射就可以创建User对象并进行属性的填充。详细信息参考fastjson的源码。
泛型的局限性
java中的泛型由于是在后期添加的特性,为了向前兼容,不能做到像C++一样完全的泛型,还是有局限性的,例如,不能 new一个泛型或者数组,T t = new T(),不能调用泛型上限中不存在的方法,即使我们知道所指定的泛型对象中有此方法。
public class Animal<T>{
//编译报错
//T t = new T();
//T [] ts = new T[10];
public void say(T t){
//编译报错
t.say();
}
}
总结
泛型的出现,把很多错误转移到编译时期,还是给开发者带来极大的方便的,不至于等到运行时才发现错误