反射(Reflection) 是⼀种运⾏时检视(Inspect)类型信息并且修改属性,调⽤⽅法的机制.它常常⽤于如下⼀些场景中
JDK中关于反射的相关类型都在java.lang.reflect包下,并不需要额外的第三⽅包来完成反射的⼯作.2. Class对象
Class对象就是⼀个类被加载到jvm中后的运⾏时表现.⽽反射的⾸要任务就是得到Class对象.得到Class对象的⽅法如下
这种⽅法适合只知道类的字符串表示,也就是全称的情况.如果类还没有加载它还会加载此类,如果已经加载了就会返回已加载的class对象给你,⽐如:
Class personCls = Class.forName("com.Person");
这种⽅式是此类已经预先知道的情况下就⾮常合适,⽐如下⾯的代码
Class personCls = Person.class;
Class integerCls = Integer.class;
2.2.1. 基本类型class对象
对于8个基本类型想获取其class对象信息除了可以⽤class字段的⽅式以外还可以利⽤其对应包装类型的TYPE字段来获取,⽐如:
Class intCls = int.class;
Class intCls = Integer.TYPE;
这种⽅式适合于已经有此类对象的情况下来获取类的class对象信息,⽐如下⾯的代码
Person p = new Person();
Class personCls = p.getClass();
java⽅法的返回类型中有⼀个特殊的值就是void,其⽤java.lang.Void类来代表.所以获取此特殊的Class对象就⽤Void的class字段或TYPE字段来实现
Class<Void> clazz = Void.class;
Class<Void> clazz = Void.TYPE;
有了Class对象之后,我们就可以利⽤它来获取各种各样的信息,主要可以获取的信息有如下⼀些
获取类的名字
类的名字分为简称和全称,⽐如下⾯的类其简称为Person,全称为com.Person
package com;
public class Person{
}
通过Class对象的getModifiers⽅法获取类的修饰符,此⽅法返回的是⼀个整数,然后依赖Modifier类的⼀系列⽅法来分析getModifiers⽅法返回整数的含义.
int modifier = clazz.getModifiers();
boolean isPublic = Modifier.isPublic(modifier);
boolean isAbs = Modifier.isAbstract(modifier);
可以通过Class对象对的getPackage()⽅法获取类的包信息,此⽅法返回的是Package类型,通过此Package类型就可以得到包的名字,⽐如:
Package pkg = clazz.getPackage();
String pkgName = pkg.getName();
获取⽗类信息主要是靠getSuperClass()⽅法实现,如果当前的Class对象代表的是Object类型,接⼝类型,void类型,基本类型,那么此⽅法返回null值
Class<?> superClazz = clazz.getSuperclass();
String superClassName = superClazz.getSimpleName();
可以通过getInterfaces⽅法获取实现或继承的接⼝信息.如果当前的Class对象代表的是⼀个类,那么此⽅法得到是此类声明实现的所有接⼝信息,不包含其⽗类实现的接⼝信息.返回的数组中按声明的顺序排序.如果没有实现接⼝就返回⻓度为0的数组.
如果当前的Class对象代表的是⼀个接⼝,那么此⽅法返回的是此接⼝extends的所有接⼝信息,返回的数组中按照声明的接⼝顺序排序.如果没有继承任何接⼝,返回的数组是⼀个⻓度为0的数组.
如果当前的Class对象代表的是void或者基本类型,此⽅法返回⻓度为0的数组.
如果当前的Class对象代表的是数组类型,那么返回的是Cloneable和Serializable
Class<?>[] personInterfaces = clazz.getInterfaces();
通过getConstructors⽅法可以获取类的所有构造函数.⽐如下⾯的⽅法
Constructor<?>[] constructors = clazz.getConstructors();
字段分为类⾃⼰声明(Declare)的和从⽗类型继承过来的字段,想得到所有的字段(不区分是继承的还是⾃⼰声明的)就通过getFields⽅法,通过getDeclaredFields()⽅法可以获取此Class对象代表的类声明的所有字段,不包括继承过来的字段.⽐如下⾯的代码
Field[] fields = clazz.getDeclaredFields();
上⾯的⽅法可以得到任意修饰符的字段,静态与实例字段可以得到,public,private等访问修饰符的字段也可以得到.如果想得到某⼀个具体名字的字段可以通过getDeclaredFields(String name)⽅法获取
Field f = clazz.getDeclaredFields("字段名");
如果Class对象代表的类型没有字段或者代表的是数组类型,基本数据类型或者void类型会返回⼀个⻓度为0的数组.
返回数组中的元素并没有进⾏排序,并且并没有特定的顺序,⽐如按照声明的顺序,所以你的代码不能依赖反射中得到的字段的顺序来编写逻辑.
⽽getFields⽅法会返回⾃身和继承过来的所有public字段.并不包含其它修饰符的字段.
⽅法也分为类⾃⼰声明(Declare)的和从⽗类型继承过来的.可以通过getDeclaredMethods()⽅法获取此Class对象代表的类声明的所有⽅法,通过getMethods()⽅法获取所有的⽅法.⽐如下⾯的代码
Method[] methods = clazz.getDeclaredMethods();
如果想获得特定的⽅法,就需要传递⽅法名与参数类型,因为⽅法有重载.⽐如下⾯的代码表示取得只有⼀个String类型参数的⽅法doSth.
Method method = clazz.getDeclaredMethods("doSth",String.class)
getDeclaredMethods的第⼆个参数是可变⻓度的,因为⽅法的参数可以有多个.
如果⼀个类只有静态代码块,那么getDeclaredMethods返回的数组中不包括这个静态代码块.
如果Class对象代表的类型没有⽅法或者代表⼀个数组,基本类型,void类型,那么返回的是⻓度为0的数组.
返回数组中的元素并没有进⾏排序,并且并没有特定的顺序,⽐如按照声明的顺序,所以你的代码不能依赖反射中得到的⽅法的顺序来编写逻辑.
⽽getMethods⽅法会返回⾃身和继承过来的所有public⽅法.并不包含其它修饰符的⽅法.
可以通过Class对象直接实例化⼀个类的对象出来.⽐如下⾯的代码
Class<Person> clazz = ...;
Person p = clazz.newInstance();
这个⽅法必须要求类有⼀个nullary constructor,也就是⽆参的构造函数.默认构造就属于nullaryconstructor.如果没有这样的构造函数,那么调⽤newInstance()⽅法时会抛出异常.
如果没有⽆参的构造函数,就不能这样实例化对象了,只能靠反射先获取Constructor对象,然后通过Constructor来创建对象.
默认构造函数与⽆参构造函数这2个术语有⼀点点的区别.默认构造函数⽐较强调由编译器⽣成
(c++领域不强调编译器⽣成,⾃⼰写的也算默认构造函数)以及是public修饰符的,⽆参构造函数不强调是public的,也不强调是编译器⽣成
/https://en.wikipedia.org/wiki/Default_constructor
https://en.wikipedia.org/wiki/Nullary_constructor
Class对象的isInstance⽅法的作⽤等价于instanceof操作符.⽐如下⾯的代码会返回true
Person person = new Person();
Class<Person> clazz = Person.class;
System.out.println(clazz.isInstance(person));
由于⼀个类可以有多个构造函数,所以反射API提供了getConstructors()⽅法得到所有的构造函数,⽤getConstructor(Class>… parameterTypes)来得到某⼀个具体的构造函数.⽐如下⾯的代码
Constructor<?>[] constructors = clazz.getConstructors();
Constructor<?> cons1 = clazz.getConstructor();
Constructor<?> cons2 = birdClass.getConstructor(String.class);
如果没有对应参数类型的构造函数会抛出NoSuchMethodException异常.
有了Constructor对象之后,我们就可以调⽤调⽤其newInstance⽅法来实例化对象,其作⽤等价于new⼀个类的对象时指定调⽤这个构造函数.
cons1.newInstance();
cons2.newInsance("hello");
获得了Method对象后,我们可以获取⽅法的名字,可访问性以及调⽤⽅法等操作.⽐如下⾯的代码获取了⽅法的名字,可访问性
Method m = ...;
boolean access = m.isAccessible();//获取可访问性
String name = m.getName();//得到⽅法名字
int parameterCount = m.getParameterCount();//得到⽅法参数个数
Parameter[] ps = m.getParameters();//获取所有参数信息
反射的⽅式调⽤⽅法主要是靠Method对象的invoke(Object obj,Object…args)⽅法来完成.其第⼀个参数是此⽅法所属类的对象,如果这个⽅法是个静态⽅法,这个参数可以传递null值,第⼆个参数就是⽅法需要的参数数据,是⼀个可变⻓度的参数.因为⽅法的参数个数可以有任意数量.
invoke⽅法的返回值就是反射⽅法调⽤后的返回值.
假设⼀个类中有下⾯的⽅法:
public void doSth(String name) {
System.out.println(name + " do sth");
}
通过反射调⽤的⽅法的实现代码如下
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
Method m = clazz.getDeclaredMethod("doSth", String.class) ;
m.invoke(p,"cj");//p.doSth("cj")
如果⽅法不能访问,仍然需要通过setAccessible来调整访问性.⽅法调⽤完毕之后再调回其访问性
Method m = clazz.getDeclaredMethod("doSth", String.class) ;
m.setAccessible(true);
m.invoke(p,"cj");
m.setAccessible(false);
⽅法的参数可以通过Method对象来获取,主要有以下⼏个⽅法来取得参数相关的信息
int paramCount = method.getParameterCount();
Class[] paramClz = method.getParameterTypes();
Parameter[] parameters = method.getParameters();
5.2.1. Parameter
反射时通过Parameter类型来代表⽅法的参数信息,通过此类型可以获取参数的名字,类型,在参数上修饰的注解,参数的修饰符等信息.
//获取⽅法的第⼀个参数
Parameter parameter = method.getParameters()[0];
String name = parameter.getName();
//得到参数的类型信息
Class[] paramClz = parameter.getParameterTypes();
int modifier = parameter.getModifiers();
//得到参数上声明的注解信息
Annotation[] annos = parameter.getDeclaredAnnotations();
其中参数名默认是得不到的,得到是arg0,arg1这样的名字,需要给编译器提供额外的参数-parameters,以便编译的时候保留参数相关的调试信息,这样反射的时候就可以获取参数的名字信息.
在IDEA中给编译器设置编译参数如下图所示,设置完成之后需要重新编译代码,这样反射就可以取得参数名信息.
我们反射得到了某个Field对象之后,就可以得到此Field的名字,类型以及给字段赋值等操作.⽐如下⾯的代码就得到了字段的名字与类型
Field field = ...;
Class<?> fieldClass = field.getType();
String name = field.getName();
int modifier = field.getModfiers();//得到修饰符
获取字段值主要是靠get()⽅法来完成,如果你确定字段的类型,可以调⽤对应类型的⽅法,⽐如调⽤getInt⽅法获取整数字段的值.这种以get开头获取字段值的⽅法主要针对基本类型,所以有8个这样的⽅法.
获取字段值分为获取静态与实例字段的值.如果是静态字段,调⽤上述⽅法获取字段时传递null,如果是实例字段的话,必须传递此字段所属类的对象给⽅法.
取得静态字段的值:
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
//sa是类的⼀个public static字段Field sa = clazz.getDeclaredField("sa");
System.out.println(sa.get(null));
System.out.println(sa.getInt(null));
取得实例字段的值:
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
Field a = clazz.getDeclaredField("a");
System.out.println(a.get(p));
System.out.println(a.getInt(p));
上⾯获取字段的值的⽅式,必须确保字段的修饰符是可以访问的,否则会抛出IllegalAccessException.
如果字段的修饰符限制了访问,可以通过修改其访问性来获取字段值或者调⽤其对应的getter⽅法来获取字段的值.⽐如下⾯的代码就是修改修饰符的⽅式来获取字段的值
Field a = clazz.getDeclaredField("a");
a.setAccessible(true);
System.out.println(a.get(p));
a.setAccessible(false);
修改可访问性只是设定是否跳过java语⾔的访问控制检查,并不是修改了字段的修饰符.
如果⼀个不能访问的字段,⽐如私有字段有对应的getter⽅法,那么你可以通过调⽤getter⽅法来获得字段的值.不过遗憾的是Field类型并没有提供获取此字段对应的getter的API.
设置值与获取值是类似的,主要是通过set⽅法以及setDouble,setInt等⽅法来完成.⽐如下⾯的代码
Field a = clazz.getDeclaredField("a");
a.setAccessible(true);
a.set(p,600);
System.out.println(a.get(p));
a.setAccessible(false);
数组是某个类型对象的⼀个合集,基于这个认识,数组的反射与普通的反射有⼀点点不同.⽐如下⾯的代码创建了⼀个⻓度为10的字符串数组.
Class cls = Class.forName("java.lang.String");
Object arr = Array.newInstance(cls, 10);
Array.set(arr, 5, "this is a test");
//设置第六个位置的字符串值String s = (String)Array.get(arr, 5);
System.out.println(s);
String[] arr2 = (String[])arr;
System.out.println(arr2[5]);
其中Array类是在java.lang.reflection包下⾯的⼀个类型.
经常会说在编译的时候会擦除所有的泛型信息,这样在运⾏时你就不能得到任何的泛型信息,这并不完全正确.在某些情况下,在运⾏时是可以得到⼀些泛型相关的信息的.
当你通过反射检查java.util.List这样的泛型接⼝时,你是不能得到任何的泛型信息的,因为这个类型不知道会参数化为哪个具体的类型.
但⼀个对参数化的泛型实例的引⽤是有办法得到⼀些泛型信息的,⽐如下⾯的代码中,如果mylist是⼀个类的字段,此字段引⽤的是⼀个实例化的泛型类型,通过mylist字段是可以得到类型实参String的.
List<String> mylist = new ArrayList<String>();
总⽽⾔之,只有通过实参化的泛型类型的引⽤来得到泛型信息,⽽不能通过泛型本身得到泛型信息,这些引⽤主要是如下⼏种情况
下⾯通过具体的代码来演示这三种情况,所有的代码都是在下⾯类的基础上进⾏的
public class MyClass {
protected List<String> stringList ;
public List<String> getStringList(){
return this.stringList;
}
public void setStringList(List<String> list){
this.stringList = list;
}
}
getStringList⽅法的返回类型是⼀个以String作为List泛型的实参的类型,想得到实参String就可以通过下⾯的⽅式来获得
Method method = MyClass.class.getMethod("getStringList", null);
Type returnType = method.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
Class typeArgClass = (Class) typeArgument;
//输出java.lang.String
System.out.println("typeArgClass = " + typeArgClass);
}
}
setStringList⽅法的参数是⼀个实参为String的List泛型类型,想通过反射得到实参String的信息的代码如下:
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
字段stringList声明的是⼀个实参为String的List类型,通过反射得到实参String的⽅法如下:
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}
反射通常⽤在bean之间的拷⻉,从Map中拷⻉数据到bean中,以及把数据库中读取的记录转换为⼀个bean等等场景中.
⽤过servlet的都知道,我们获取请求数据时需要把请求的数据封装到⼀个bean中,⽽Servlet中所有的请求的数据是放在⼀个map中的,基于这种情况,下⾯的案例是把⼀个map中的数据赋值给⼀个bean对象,map中键的名字作为bean的字段的名字,map中键对应的值作为对应字段的值.map的数据如下:
Map<String,Object> map = new HashMap<>();
map.put("id",100);
map.put("name","cj");
map.put("age",18);
bean类的代码如下:
public class UserInfoEntity {
private Integer id;
private String name;
//省略getter,setter,toString()
}
实现把map中的数据拷⻉到bean的代码如下,其中map中的age键在map中没有对应的字段,所以getDeclaredField时会抛出异常,但我们捕获了.所以并不影响最终bean实例的⽣成.
public static void main(String[] args) throws Exception {
Map<String,Object> map = new HashMap<>();
map.put("id",100); map.put("name","cj");
map.put("age",18);
UserInfoEntity userinfo = copyData(map,UserInfoEntity.class);
System.out.println(userinfo);
}
public static <T> T copyData(Map<String,Object> source,Class<T> targetClass) throws
Exception {
T instance = targetClass.newInstance();
for(Map.Entry<String,Object> entry: source.entrySet()) {
try {
String key = entry.getKey();
Field f = targetClass.getDeclaredField(key);
f.setAccessible(true);
f.set(instance, entry.getValue());
f.setAccessible(false);
}catch(NoSuchFieldException e) {
e.printStackTrace();
}
}
return instance;
}
最终输出的结果是:
UserInfoEntity [id=100, name=cj]