java随笔系列-泛型3

/ java随笔系列 / 没有评论 / 39浏览

前言

距离上两篇的文章,已经过了一个月了,主要是中间有个10月1再加上项目的事情有点忙,所以泛型的第三篇一直没有总结。今天趁着周六日总结一下关于泛型真实类型获取和http请求库的序列化设计问题

泛型类型获取

在java中由于泛型擦除机制,所以局部变量的泛型和运行期泛型设置是没有办法获取到具体类型的。

    public static void fetchList(List objects) {
        //无法获取到运行期objects的泛型类型
    }

    public static void getFetchList() {
        List<String> strings = new ArrayList<>();
        fetchList(strings);//无法获取到
    }

但是也有一些情况可以获取到泛型的真实类型

  1. 类的字段-实例变量,类变量
  2. 方法的参数,返回类型
  3. 类的声明的泛型类型
public class SuperGenerics<T> {
    private T t;
     public SuperGenerics(T t) {
        this.t = t;
    }
}
public class GenericsHolder extends SuperGenerics<String> { 
    private List<String> tList = new ArrayList<>();
    public GenericsHolder(String s) {
        super(s);
    }
    public List<String> getAndPutList(List<String> list) {
        return tList;
    }
}
@Test
public void testFieldHold() throws Exception {
    GenericsHolder genericsHolder = new GenericsHolder("god");
    //类的泛型
    Type type = genericsHolder.getClass().getGenericSuperclass();
    if (type instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) type;
    }
    //成员变量的类型。如果有泛型的话,就是parameterizedType,其他则是class等
    Field field = genericsHolder.getClass().getDeclaredField("tList");
    Type filedType=field.getGenericType();
    //方法的返回类型,参数类型
    Method method=GenericsHolder.class.getDeclaredMethod("getAndPutList");
    Type returnType=method.getGenericReturnType();//如果含有泛型,则是ParameterizedType
    Type[] paramTypes=method.getGenericParameterTypes();//如果param有泛型,则该type是ParameterizedType
}

java类型体系Type

在java引入泛型前,只有一个原始类型就是class,代表了一切,引入了泛型后,由于java中的泛型并不是最初的组成成分(C#就是真的泛型)如果真的加入了泛型,会涉及到jvm指令集的修改,会造成很严重的问题,所以采用了泛型擦出技术,一旦编译完成,与泛型有关的参数化类型、类型变量类型、限定符类型 、泛型数组类型等并不存在对应的字节码文件中,所以不能把这些东西统一规划到class,为了解决此问题,提供程序的扩展性,能够让开发者通过反射获取相关的类型,引入了Type,ParameterType等等类,Type是一切类型的基类,其子类有class(原始类型),ParameterizedType(泛型类型),GenericArrayType(泛型数组类型),WildcardType(通配符泛型),TypeVariable(类型变量)。

ParameterizedType类型

其java定义如下:

public interface ParameterizedType extends Type {
    Type[] getActualTypeArguments(); //实际泛型类型列表
    Type getRawType(); //承载泛型的对象类型
    Type getOwnerType();//表示此类型所属的类型。如果是顶级类型,则返回null
}

Map<String,Intenger>为例:

   private Map<String, Integer> integerMap;
    @Test
    public void testParameterType() throws NoSuchFieldException {
        Field field = GenericsTest.class.getDeclaredField("integerMap");
        Type type = field.getGenericType();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            System.out.println("rawType " + parameterizedType.getRawType());
            System.out.println("ownerType " + parameterizedType.getOwnerType());
            for (Type type1 : parameterizedType.getActualTypeArguments()) {
                System.out.println("actualType " + type1.getTypeName());
            }
        }
    } 
//输出结果:
    rawType interface java.util.Map
    ownerType null
    actualType java.lang.String
    actualType java.lang.Integer

WildcardType通配符类型

WildcardType定义如下:

public interface WildcardType extends Type {
    Type[] getUpperBounds();//上边界
    Type[] getLowerBounds();//下边界
}

该接口表示通配符类型, 比如? extends Number 和 ? super List。

    private List<? extends Number> numbers;
    @Test
    public void testWildcardType() throws NoSuchFieldException {
        Field field = GenericsTest.class.getDeclaredField("numbers");
        Type type = field.getGenericType();
        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type[] types=parameterizedType.getActualTypeArguments();
            WildcardType wildcardType= (WildcardType) types[0];
            System.out.println(wildcardType.getUpperBounds()[0]);//class java.lang.Number
        }
    }
    
    输出:
        class java.lang.Number

一些常见的例子:

List<? extends Number>, 上界为class java.lang.Number, 属于Class类型
List<? extends List<T>>, 上界为java.util.List<T>, 属于ParameterizedType类型
List<? extends List<String>>, 
List<? extends T>, 上界为T, 属于TypeVariable类型
List<? extends T[]>, 上界为T[], 属于GenericArrayType类型

WildcardType和泛型的协变和逆协变有关

TypeVariable类型变量

TypeVariable类型变量用来存储在JVM编译该泛型前的信息,,其声明如下:

public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
    Type[] getBounds();//获取类型变量的上边界, 若未明确声明上边界则默认为Object,可以是多继承所以是数组
    D getGenericDeclaration();//获取声明该类型变量实体
    String getName();//获取在源码中定义时的名字
    AnnotatedType[] getAnnotatedBounds();
}

通过其源码声明,可以看出TypeVariable和GenericDeclaration有一定的关联性,TypeVariable是指在泛型声明这些东西中的变量T、C。而GenericDeclaration似乎指代的就是他们的整体。

    @Test
    public void testTypeVariable() throws NoSuchMethodException {
        Method method=GenericsTest.class.getDeclaredMethod("typeVariable",new Class[]{Comparable.class});
        Type type=method.getGenericParameterTypes()[0];
        if(type instanceof TypeVariable){
            TypeVariable typeVariable= (TypeVariable) type;
            System.out.println("typeVariable name "+typeVariable.getName());
            GenericDeclaration genericDeclaration=typeVariable.getGenericDeclaration();
            System.out.println("typeVariable =? genericDeclaration.getTypeParameters[0] "+(genericDeclaration.getTypeParameters()[0]==typeVariable));
            for(Type type1:typeVariable.getBounds()){
                System.out.println("typeVariable bounds "+type1);
            }
        }
    }

    public <T extends Comparable & Serializable> void typeVariable(T t) {
    }
    输出结果:
    typeVariable name T
    typeVariable =? genericDeclaration.getTypeParameters[0] true
    typeVariable bounds interface java.lang.Comparable
    typeVariable bounds interface java.io.Serializable

多继承的泛型方法可以通过第一个继承接口作为方法的参数进行反射

GenericArrayType数组泛型

范型数组,组成数组的元素中有范型则实现了该接口; 它的组成元素是ParameterizedType或TypeVariable类型。他和其他的类型相比比较简单,就不说了。

泛型序列化的应用-网络库

前面说了概念性的东西,那么我们理解这些概念最终要为了我们软件设计作为铺垫,下面我以最常见的网络请求库作为实践的例子。

多方法版本

我们app的网络请求返回的结果很多都是json格式,然后通过Gson等序列化出相应的bean,在设计网络请求的库的时候都会把bean的序列化对象class作为参数传到网络请求库,然后通过callback获取其实例。

//结果对象
public class Result<T> {
    private int status;
    private String message;
    private T t;//数据内容
    ...
}
//请求回掉函数
public interfac MyCallback<T> {
    public void onResponse(T t);
    public void onError();
}
public static <T>  void request(BaseRequest baseRequest, Class<T> tClass, MyCallback<T> tMyCallback) {
  //处理网络请求,并执行回调
}

这种设计方案满足大部分的情况,但是如果我们返回的数据是List集合,集合内的对象是我们需要的序列化对象时,就存在问题了,因为并不存在List.class这中类型,所以我们内部就无法获取其真正的序列化对象的bean了。所以我们需要在设计一个方法,来专门处理这种情况,

    //内部使用ParameterizedType作为type,然后网络处理模块通过对type的判断进行序列化处理操作
    public static <T> void requestArray(BaseRequest baseRequest, Class<T> tClass, MyCallback<List<T>> listMyCallback) {
        requestType(baseRequest, new ParameterizedType() {
            @Override
            public Type[] getActualTypeArguments() {
                return new Type[]{tClass};//放入序列化对象的类型
            }
            @Override
            public Type getRawType() {
                return List.class;
            }
            @Override
            public Type getOwnerType() {
                return null;
            }
        }, listMyCallback);
    }
    //内部使用Type作为参数
    private static void requestType(BaseRequest baseRequest, Type type, MyCallback<?> myCallback) {
        //处理网络请求,并执行回调函数
    }

设计两个函数requestArrayrequest分别处理不同的情况,但是序列化的对象还有泛型类型的话那么就还需要设计更多的函数,总觉的不太理想。

多方法的改良版

我们的网络框架需要保留泛型类型,这样就不需要专门处理泛型嵌套的情况,retrofit的思路是声明接口然后采用动态代理的方式,接口的泛型信息是可以保留到运行期的,所以retrofit的网络框架是可以通过接口的信息进行序列化的。那么我同样可以在request的请求对象中直接把需要的序列化对象以方法或者实例变量的方式声明出来。

public abstract class BaseRequest {
    public abstract Type resultType();
    public abstract Type returnType();
    ...other func
}
public class StudentRequest extends BaseRequest {
    @Override
    public Type resultType() {
        return new ParameterizedType() {
            @Override
            public Type[] getActualTypeArguments() {
                return new Type[]{Student.class};
            }
            @Override
            public Type getRawType() {
                return List.class;
            }
            @Override
            public Type getOwnerType() {
                return null;
            }
        };
    }
    @Override
    public Class<List<Student>> returnType() {
        return null;
    }
}

StudentRequest中,我用两中形式保留的序列化的类型,一中是通过返回的真实type,一个是通过方法声明的返回参数。这两个方式都可以有效的保留泛型类型,我们只需要在网络库的解析库中作出相应的处理即可。

结尾

本文主要详细介绍了泛型的体系结构,以及如果利用这些特性进行软件设计等。