泛型广泛用于各类框架中,日常开发、或是源码阅读时,泛型的的曝光率都很高,搞懂泛型很有必要。泛型的基础使用场景比较简单,但在复杂场景下往往并不容易理解
本文首先介绍了java泛型基础概念,随后列举了泛型的一些复杂使用场景,最后介绍了spring中强大易用的泛型API,希望帮你更深入的理解泛型,也可以在日常开发工作时更好的利用泛型进行通用设计
java泛型定义
泛型允许用户在定义class、interface、method时,声明一个或多个类型参数(type parameter)。和方法参数一样,定义好类型参数后,就可以在对应作用域中(class中、interface、method中)引用该参数。不同之处是,方法参数的输入是值、而类型参数的输入是type
相关名词解释:
泛型类型(generic type):包括泛型类和泛型接口,定义格式:
public class Generics<T1, T2, ..., Tn> {
...
}
泛型方法(generic method):包括普通方法和构造函数,静态和非静态方法都支持泛型,注意泛型参数必须在方法返回类型前,定义格式:
public <T1, T2, ..., Tn> void test(T1 t1,T2 t2...,Tn tn){
...
}
类型参数/类型变量(type parameter/type variable):泛型类型或方法声明中的"<>"包含的T1、T2..就是type parameter(也称为type variable)
类型实参(type argument):注意不要和type parameter混淆了,type parameter是形参(声明时定义的参数),type argument是实参(实际传入的参数)。比如class Foo<T>{}中的T是形参(type parameter),而Foo<String> foo;中的String是实参(type argument)
参数化类型(parameterized type):Foo<String> foo;中的Foo<String>就是parameterized type
有界泛型(bounded Generics):可以通过限制type parameter的边界,来限制泛型类型、泛型方法接受的type argument的类型范围,比如下面的泛型方法限定只能接受Number及其子孙类作为输入
public <T extends Number> List<T> fromArrayToList(T a) {
...
}
泛型通配符(wildcards):使用?表示泛型通配符,用于表示未知类型,下文的场景描述中会作详细说明
有界泛型通配符(bounded wildcards):同样的,通配符也支持边界限制:
public static void paintAllBuildings(List<? extends Building> buildings) {
...
}
原始类型(raw type):参数化类型(parameterized type)去掉类型实参(type argument)之后就是raw type,Foo<String>中的Foo就是raw type
泛型复杂使用场景
泛型的基础使用场景我们就不展开了,下面介绍一些稍微复杂点的场景
场景一:
public void someMethod(Number n) { /* ... */ } someMethod(new Integer(10)); // OK someMethod(new Double(10.1)); // OK List<Number> list = new ArrayList<Number>(); list.add(new Integer(10)); // OK list.add(new Double(10.1)); // OK
毫无疑问,上述两个场景都是OK的,原因是Integer、Double是Number的子类型,那么我们考虑下下面这个方法可以传入什么类型的参数:
public void test(List<Number> n) {}
List<Integer>、List<Double>可以吗?答案是不可以,因为List<Integer>、List<Double>并不是List<Number>的子类,ArrayList<Number>、LinkedList<Number>是可以的,它们是List<Number>的子类
场景二:
写一个打印list元素的方法,要求支持任意类型的list
public static void printList(List<Object> list) { for (Object elem : list){ System.out.println(elem + " "); } }
显然这个方法无法满足要求,因为它只能处理List<Object>类型的list,List<String>、List<Integer>都不行,原因在场景一中已经说过
如何实现呢?用泛型通配符
public static void printList(List<?> list) { for (Object elem: list) System.out.print(elem + " "); System.out.println(); }
"?"表示泛型通配符,常用于这两个场景:一种是独立的泛型方法,该方法希望支持任何继承于Object的类型(比如上面的printList方法);二是泛型类中的方法,虽然泛型类已经定义了type parameter,但是方法并不依赖于该type parameter,比如List接口中的removeAll方法
public interface List<E> extends Collection<E>{ boolean removeAll(Collection<?> c); 。。。 }
虽然List接口已经定义了type parameter(E),但是removeAll方法的功能是从当前集合中移除在输入集合中存在的元素,输入集合可能包含E类型的元素,但是也可能包含其他类型的元素,因此用Collection<?>而不是Collection<E>
注意,只有方法才会用到<?>,如果你看到某个类上用到了,那一定是作者理解有误
场景三:
//定义一个泛型接口A interface A<T>{}
下面这几种实现方式你一定都遇到过:
//方式一 class B implements A<String>{} //方式二 class B<T> implements A<T>{} //方式三 class B<T extends User> implements A<T>{} //方式四 class B<T> implements A<String>{} //方式五 class B<T,E> implements A<T>{}
那么它们分别代表什么含义呢?
方式一:子类B指定类型实参(type argument)为String,此时B已经不是一个泛型类了,在使用B时,也不再需要指定type argument了
方式二:子类B原封不动的继承了父类的类型参数(type parameter),在使用B时,可以指定任意类型的类型实参(type argument)
方式三:子类B重新定义了从父类继承来的类型参数(type parameter),并通过有界泛型(bounded Generics)将其范围缩小为必须是User及其子孙类,在使用B时,只能指定类型为User及其子孙类的类型实参(type argument)
方式四:子类B指定父类的类型实参(type argument)为String,并定义了自己的类型参数(type parameter)(T),这个T跟父类没有任何关系
方式五:子类B继承了父类的类型参数(type parameter)(T)、并且定义了自己的type parameter(E),在使用B时,需要指定两个类型实参(type argument)
场景四:
抽象模板类A的handle方法首先将输入的json串转为T类型对象,然后交给子类处理
public abstract class A<T>{ public void handle(String json){ //通过json序列化框架将json转换为T类型的实例,然后交给子类处理 T t=fromJson(json); doHandle(t); } private T fromJson(String json) { //如何实现? } protected abstract void doHandle(T t); }
考虑下如何fromJson方法如何实现,json转为对象得先拿到对应的Class,怎么拿呢?T.class你猜猜行不行?。。。正确的解法如下,你可以结合上面的名词解释理解下:
public abstract class A<T>{ public void handle(String json){ T t=fromJson(json); doHandle(t); } private T fromJson(String json) { //获取父类的参数化类型(parameterized type):A<User> ParameterizedType parameterizedType=(ParameterizedType)this.getClass().getGenericSuperclass(); //获取其类型实参数组(type arguments):new Type[]{User.class} Type[] typeArguments=parameterizedType.getActualTypeArguments(); //获取第一个类型实参(type argument):User.class Class<T> clazzT=(Class<T>)typeArguments[0]; return new Gson().fromJson(json,clazzT); } protected abstract void doHandle(T t); } public class B extends A<User>{ @Override protected void doHandle(User user) { //do something } }
场景五:
class C<T>{},如何在C中获取T对应的type argument?答案是没有任何办法获取到,在场景三中能拿到是因为type argument在编译期间就已确定下来了(class B extends A<User>),当前场景中,编译时只有type parameter没有type argument,而针对泛型的处理都是在编译期间完成的,试图从运行时获取泛型信息是行不通的
泛型解析工具ResolvableType:
如果你在项目中实现过通用功能,你应该有过泛型解析的需求(比如场景四),Spring提供了一个强大的泛型解析工具ResolvableType:ResolvableType封装一个java.lang.reflect.Type对象,提供了对Type上各种信息的访问能力,比如对父类(getSuperType())、接口(getInterfaces())、类上泛型参数的访问能力(getGeneric(int...))、以及将泛型参数解析为class的能力(resolve())
可以通过field、method parameter、method return type或者class来构造ResolvableType,分别对应ResolvableType中的静态方法:forField(Field)、forMethodParameter(Method, int)、forMethodReturnType(Method)、forClass(Class)
下面通过几组例子感受下ResolvableType的能力、以及其易用性
//需求一、解析指定field的泛型信息
public class Generics{
private HashMap<Integer, List<String>> myMap;
}
public void resolveFieldGenerics() throws NoSuchFieldException {
//通过field构造一个ResolvableType对象
ResolvableType t = ResolvableType.forField(getClass().getDeclaredField("myMap"));
//获取父类并解析为class->java.util.AbstractMap
System.out.println(t.getSuperType().resolve());
//解析方法第一个type argument的类型,结果为:java.lang.Integer
System.out.println(t.getGeneric(0).resolve());
//效果同上,结果为:java.lang.Integer
System.out.println(t.resolveGeneric(0));
//解析方法第二个type argument的类型,结果为:java.util.List,注意:如果type argument依然是一个泛型类型,则resolve返回的是原始类型(raw type)
System.out.println(t.getGeneric(1).resolve());
//效果同上,结果为:java.util.List
System.out.println(t.resolveGeneric(1));
//解析list第一个type argument的类型,结果为:java.lang.String
System.out.println(t.getGeneric(1).getGeneric(0).resolve());
//效果同上,结果为:java.lang.String
System.out.println(t.resolveGeneric(1, 0));
}
//需求二、解析指定方法返回类型的泛型信息
public Map<String, List<Integer>> test(){return null;}
void resolveMethodGenerics() throws NoSuchMethodException {
//通过方法返回类型构造一个ResolvableType对象
ResolvableType methodReturnType=ResolvableType.forMethodReturnType(getClass().getMethod("test"));
//->java.lang.String
System.out.println(methodReturnType.resolveGeneric(0));
//->java.util.List
System.out.println(methodReturnType.resolveGeneric(1));
//->java.lang.Integer
System.out.println(methodReturnType.resolveGeneric(1, 0));
}
//需求三、解析类的泛型信息,是前面举过的例子,这里通过ResolvableType再实现一遍,你可以跟场景四中用jdk自带api实现的方式做个对比
public abstract class A<T>{
public void handle(String json){
T t=fromJson(json);
doHandle(t);
}
private T fromJson(String json) {
//通过class构造一个ResolvableType对象,并将其作为父类的ResolvableType返回
ResolvableType classResolvableType=ResolvableType.forClass(this.getClass()).as(A.class);
//->User
Class<T> clazzT=classResolvableType.resolveGeneric(0);
return new Gson().fromJson(json,clazzT);
}
protected abstract void doHandle(T t);
}
public class B extends A<User>{
@Override
protected void doHandle(User user) {
//do something
}
}
几乎你能想象出来的泛型解析场景,ResolvableType都可以帮你解决,更关键的是解决方式都比较优雅,希望你能掌握