轻触开源(一)-Java泛型Type类型的应用和实践

柔情痞子 提交于 2019-12-30 19:14:32

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

转载请注明出处:https://my.oschina.net/u/874727/blog/747427

Q:1025250620

在很多Java的开源项目中都用到Java的泛型。比如Gson,就可以通过TypeToken<T>里的泛型参数来指定生成的类型。鉴于网上关于泛型的文章并不多,为了非墨后面项目研究的需要,非墨开始研究这部分的API。首先我们先来看一下在Java语言中泛型的例子:

public class MyTest<T1,T2 extends Number> {

   T1 member;

   public <T> void method(T m) {}

}

上述代码中的标志:T1,T2,T都是泛型类型。Java的泛型检查发生在编译期,但是会在编译后的JVM字节码中增加类型判断的语句。为了方便大家理解这句话我们用一段代码测试一下:

List<String> list = new ArrayList<>();
		try {
			Method m = list.getClass().getDeclaredMethod("add", new Class[]{Object.class});
			m.invoke(list, 1);
			m.invoke(list, 2);
		} catch (Exception e) {
			System.out.println("error");
		} 

在上述代码中,虽然List变量指定了内部元素的类型String,但是在JVM运行期间list对象的add方法调用的还是add(Ljava/lang/Object)签名的方法。所以此段代码执行以后,控制台不会有任何的输出。基于此段代码的基础上,我们增加另一段验证代码:

for (Object o:list) {
			System.out.println(">>"+o);
		}//println >>1 >>2
		
		//will error
		System.out.println("--->"+list.get(0));

我们打印list中的子元素,如果子元素类型是Object类型的话代码正常运行。但是如果我们直接通过list.get的方式来获取元素的时候程序就会抛出一个Class Cast异常。这是为什么呢?我们来看一下编译后的JVM字节码:

   95:	iconst_0
   96:	invokeinterface	#56,  2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
   101:	checkcast	#62; //class java/lang/String

我们看到,Java编译器在执行段后插入了"checkcast"指令。也就是说Java的泛型只不过是在执行期间增加了一些类型的检查语句。但是,尽管泛型发生在编译器,但是java还是把类型记录在类型对象中。这就是我们今天讨论的主角Type对象。我们看一下Type类型的继承树:

可以看出,Type类型有四个直接接口子类,一个实现类。此外还有另外一个接口GenericDeclaration。这个接口注明哪个类可以声明泛型。按照我们通过第一代码块可以知道,在Java语言中,可以声明泛型的是类,方法。为什么没有成员变量呢?我们在第一个代码块中的"T1 member"不也是泛型声明么?

或许这里我们应该换一个说法,对于member变量来说,它只是使用了泛型而并不是声明了泛型,声明T1泛型的是MyTest类。我们把构造器也看成方法的一部分,方法作为可执行提,在方法和构造器类的基础上JAVA又做了一层抽象--Executable类。

Type类型的四个直接子类注释里已经给出了解释。由于这些东西在javadoc中也说的并不详细,也很难用辞藻把他描述的非常清楚。为了让大家理解,非墨用一些简单的代码来让大家更加直观的理解他们是个什么东东。我们先将可能涉及到的泛型类型和声明方式都写到一个类中:

public static class TypeClazz<T1, T2 extends Number> {
		
		public T2 member;
		public T1 member2;
		public Collection<? extends Number> collection;
		public Collection<T2> collection2;
		public T2[] array;
		public <T extends Type> void method(T p1,T2 p2) {}
	}

这个类基本涵盖了所有的泛型情况。我们还需要增加一些方法来打印我们所关心的信息:

public static Type printlnFieldType(String name) {
		System.out.println("name:"+name);
		Class clazzType = TypeClazz.class;
		Type type = null;
		try {
			Field field = clazzType.getDeclaredField(name);
			type = field.getGenericType();
			printlnType(field.getGenericType());
		} catch (Exception e) {} 
		if (type instanceof ParameterizedType) {
			ParameterizedType ptype = (ParameterizedType)type;
			Type[] types = ptype.getActualTypeArguments();
			for (Type t:types) {
				System.out.print(">>");
				printlnType(t);
			}
		}
		return type;
	}
	
	public static void printlnMethodReturnType(String name) {
		System.out.println("name:"+name);
		Class clazzType = TypeClazz.class;
		try {
			Method[] ms = clazzType.getDeclaredMethods();
			Method method = null;
			for (Method m:ms) {
				if(m.getName().equals(name)) {
					method = m;
					break;
				}
			}
			printlnType(method.getGenericReturnType());
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
	
	public static void printlnMethodParamTypes(String name) {
		System.out.println("name:"+name);
		Class clazzType = TypeClazz.class;
		try {
			Method[] ms = clazzType.getDeclaredMethods();
			Method method = null;
			for (Method m:ms) {
				if(m.getName().equals(name)) {
					method = m;
					break;
				}
			}
			Type[] types = method.getGenericParameterTypes();
			for (Type t:types) {
				printlnType(t);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}

对于属性,我们只关心它的类型,而对于方法,我们不仅需要关心它的参数类型,还要关心它的返回类型。我们来调用一下以上的信息:

public static void main(String ...args) {
		printlnFieldType("member");
		printlnFieldType("member2");
		printlnFieldType("collection");
		printlnFieldType("collection2");
		printlnFieldType("array");
		printlnMethodReturnType("method");
		printlnMethodParamTypes("method");
	}

控制台输出:

name:member
>>>TypeVariable
name:member2
>>>TypeVariable
name:collection
>>>ParameterizedType
>>>>>WildcardType
name:collection2
>>>ParameterizedType
>>>>>TypeVariable
name:array
>>>GenericArrayType
name:method
>>>class
name:method
>>>TypeVariable
>>>TypeVariable

对于直接采用泛型方式定义的member来说,它都是TypeVariable类型。而对于包含有泛型定义的collection来说,它属于参数化的ParameterizedType类型。由于数组类型是单独的类型,并且由虚拟机动态生成,因此,Type子类中有专门针对数组的GenericArrayType类型。例子中的array成员就是这种类型。我们揭开了collection的面纱后,泛型定义为"<? extends Number>"。这个就是通配符泛型WildcardType。

相信以上的例子已经很能解释这四种类型的定义了,下一篇,非墨将带着这些基础知识,深入到一个开源项目的源码中,看下别人的项目是如何巧妙的运用这点的。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!