Java反射进阶
上一篇文章介绍了反射的部分基础知识点,本篇文章继续进一步探讨反射,主要涉及两方面:反射操作泛型以及反射操作注解。事实上反射操作注解跟上一篇类似,只是使用了field.getAnnotation()
这种方式,所以本篇文章重点还在于对泛型的处理,在此之前我们先介绍一下java.lang.reflect.Type这个类。
Type的简单介绍
一般在使用反射的时候都是直接使用Class类,后来接触Gson对json操作的时候,突然发现它是有这么一个常用的方法public <T> T fromJson(JsonReader reader, Type typeOfT)
,那时候使用的是eclipse,然后按住的Ctrl键进去看了一下Type源码,发现Class原来实现了Type接口。
Type是所有类型的父接口, 如原始类型(raw types 对应 Class)、 参数化类型(parameterized types 对应 ParameterizedType)、 数组类型(array types 对应 GenericArrayType)、 类型变量(type variables 对应 TypeVariable )和基本(原生)类型(primitive types 对应 Class)。Type的源码很简单,里面就一个方法。
public interface Type { default String getTypeName() { return toString(); } }
泛型出现之后,扩充了数据类型。从只有原始类型扩充了参数化类型、类型变量类型、泛型限定的的参数化类型 (含通配符+通配符限定表达式)、泛型数组类型。
与泛型有关的类型不能和原始类型统一到Class的原因利用反射操作泛型
[1]. 【产生泛型擦除的原因】
本来新产生的类型+原始类型都应该统一成各自的字节码文件类型对象。但是由于泛型不是最初Java中的成分。如果真的加入了泛型,涉及到JVM指令集的修改,这是非常致命的。
[2]. 【Java中如何引入泛型】
为了使用泛型的优势又不真正引入泛型,Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除。
[3]. 【Class不能表达与泛型有关的类型】
因此,与泛型有关的参数化类型、类型变量类型、泛型限定的的参数化类型(含通配符+通配符限定表达式)、泛型数组类型这些类型全部被打回原形,在字节码文件中全部都是泛型被擦除后的原始类型,并不存在和自身类型一致的字节码文件。所以和泛型相关的新扩充进来的类型不能被统一到Class类中。
[4]. 与泛型有关的类型在Java中的表示
为了通过反射操作这些类型以迎合实际开发的需要,Java就新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
[5].【引入Type的原因】
Type的引入统一与泛型有关的类型和原始类型Class为了程序的扩展性,最终引入了Type接口作为Class,ParameterizedType,GenericArrayType,TypeVariable和WildcardType这几种类型的总的父接口。这样实现了Type类型参数接受以上五种子类的实参或者返回值类型就是Type类型的参数。从上面看到,Type的出现仅仅起到了通过多态来达到程序扩展性提高的作用,没有其他的作用。 使用泛型与不使用泛型在编译后的.class文件一样,那么泛型信息都被存入哪去了呢?可以参考Where are generic types stored in java class files?。
反射操作泛型简单示例
获取父类泛型的实际类型
在进行示例演示之前,先看一个简单的JavaBean定义。
public class KeyValue{ private K key; private V value; public KeyValue(){} public KeyValue(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } public void setValue(V value) { this.value = value; } @Override public String toString() { return "KeyValue [key=" + key + ", value=" + value + "]"; } }
然后我们自定义一个User类继承自KeyValue类。
public class User extends KeyValue<String, Integer> { }
该示例演示操作的目的很简单,主要是看一下User类继承KeyValue后所实现泛型的具体类型,那么如何来看父类中泛型的具体类型呢?事实上Class类提供了获取父类Class实例的方法,一个是getSuperclass()方法,另一个是getGenericSuperclass()方法,第一个方法获取的没有泛型信息的父类信息,第二个方法可以将父类泛型信息获取到。
com.yimi.demo.bean.KeyValue//getSuperclass() com.yimi.demo.bean.KeyValue<java.lang.String, java.lang.Integer>//getGenericSuperclass()
接下来看一个工具方法的定义。
public static Type[] getParameterizedTypes(Object object) { Type superclassType = object.getClass().getGenericSuperclass(); if (!ParameterizedType.class.isAssignableFrom(superclassType.getClass())) { return null; } return ((ParameterizedType)superclassType).getActualTypeArguments(); } isAssignableFrom()类似于instantceof关键字,只是instanceof关键字使用方式是sub instanceof sup,但是isAssignableFrom()方法使用则是反过来的,是sup isAssignableFrom(sub)。getParameterizedTypes()方法就是获取父类的具体泛型类型,返回的是一个数组的形式。 User user=new User(); Type[] types=getParameterizedTypes(user); for(int i=0;i<types.length;i++){ System.out.println(types[i].getTypeName()); } //java.lang.String //java.lang.Integer
从JDK1.5引入了泛型,到目前开发中使用泛型的便利不言而喻,那么反射对于泛型在实际开发中又有哪些便利性呢?先简单介绍一下今天讨论的获取父类的泛型的实际类型的用法,就目前我个人而言所接触到的有两个地方使用非常便利。其一是面向dao的开发模式,在对数据库进行操作中查插删改四种操作几乎是没一个实体类所必须的操作,但是如果使用泛型就可以直接将这四种操作集中到一个父类中进行操作。另外一种就是在解析json的时候相当简单,可以将回调方法中对象直接返回为自己想要的实例,下面的部分代码是一个队网络请求的简单模拟。
public interface ResponseCallback<T> { void callback(T t); } public abstract class HttpResponseCallback<T> implements ResponseCallback<T> { private Type type; public HttpResponseCallback(){ Type superclassType = getClass().getGenericSuperclass(); type=((ParameterizedType)superclassType).getActualTypeArguments()[0]; } public void callback(HttpResponse response){ Gson gson=new Gson(); T t=gson.fromJson(response.getResult(), type); callback(t); } }
然后在使用的时候就可以像下面这样:
//泛型是User String result = "{'name':'admin','age':20}"; HttpResponseCallback<User> callback = new HttpResponseCallback<User>() { @Override public void callback(User user) { System.out.println(user); } }; //泛型是一个List String result = "['str01','str02','str03']"; HttpResponseCallback<List<String>> callback = new HttpResponseCallback<List<String>>() { @Override public void callback(List<String> list) { System.out.println(list); } }; HttpResponse response = new HttpResponse(); response.setResponseCode(200); response.setResult(result); callback.callback(response);
反射操作方法泛型
如果使用反射操作方法泛型参数,具体的话有两种方式,一种时形参,一种时返回值,下面是两中类型方法示例:
public static void testParams(Map<String, Integer> map, List<User> list) { System.out.println("hello world"); } public static Map<String,User> testReturnParams(){ return null; }
在获取方法形参的时候使用的是getGenericParameterTypes()
方法,该方法返回的是一个Type数组,获取返回值得时候使用的是getGenericReturnType()
。
Method paramMethod=MainTest.class.getDeclaredMethod("testParams",Map.class,List.class); Type[] genericTypes=paramMethod.getGenericParameterTypes(); for(int i=0;i<genericTypes.length;i++){ System.out.println(genericTypes[i].getTypeName()); if (ParameterizedType.class.isAssignableFrom(genericTypes[i].getClass())) { Type[] types=((ParameterizedType)genericTypes[i]).getActualTypeArguments(); for(int j=0;j<types.length;j++){ System.out.println("--"+types[j].getTypeName()); } } } System.out.println("=================================="); Method returnMethod=MainTest.class.getDeclaredMethod("testReturnParams"); Type genericReturnType=returnMethod.getGenericReturnType(); System.out.println(genericReturnType.getTypeName()); if (ParameterizedType.class.isAssignableFrom(genericReturnType.getClass())) { Type[] types=((ParameterizedType)genericReturnType).getActualTypeArguments(); for(int i=0;i<types.length;i++){ System.out.println("--"+types[i].getTypeName()); } } //java.util.Map<java.lang.String, java.lang.Integer> //--java.lang.String //--java.lang.Integer //java.util.List<com.yimi.demo.bean.User> //--com.yimi.demo.bean.User //================================== //java.util.Map<java.lang.String, com.yimi.demo.bean.User> //--java.lang.String //--com.yimi.demo.bean.User
反射操作注解
对于注解的操作,本文就通过一个简答的demo介绍一下,定义一个Table注解,定义一个Column注解,然后定义一个实体类,注解上对应的Table名称以及每个属性对应的列表。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column { String column(); String type(); int length(); } @Target(value={ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Table { String name(); } public static void parse(Class<?> clazz){ //table Table table=clazz.getAnnotation(Table.class); System.out.println("表格名称:"+table.name()); //column Field fields[]=clazz.getDeclaredFields(); for(Field field:fields){ Column column=field.getAnnotation(Column.class); System.out.println("列名:"+column.column()+" 类型:"+column.type()+" 长度:"+column.length()); } } @Table(name="t_student") public class Student { @Column(column="_id",type="int",length=10) private int id; @Column(column="_name",type="varchar",length=20) private String name; @Column(column="_age",type="int",length=3) private int age; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } //表格名称:t_student //列名:_id 类型:int 长度:10 //列名:_name 类型:varchar 长度:20 //列名:_age 类型:int 长度:3
反射的介绍暂时告一段落了,如果有错误疏忽之处还请及时支持以求共同进步,后续大概会再介绍一下反射在代理中的应用。最后再补充一点,有部分第三方框架是基于反射操作的,但是它要求我们在开发过程中必须有一个无参的构造方法,一般情况下我们是不需要添加无参构造方法的,系统会默认给添加上,但是如果开发中我们定义了许多带有参数的构造方法,一定要自己添加一个无参的构造方法,因为这种情况下系统会认为已经有构造方法了,所以不会再默认给新增一个无参的构造方法,而这时候很多框架就会报错,因为框架中有类似如下的方法,它会判断类中是否有无参构造方法。
public static boolean hasDefaultConstructor(Class<?> clazz) throws SecurityException { Class<?>[] empty = {}; try { clazz.getConstructor(empty); } catch (NoSuchMethodException e) { return false; } return true; }