关于Java反射
反射是什么?
反射,是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。对于Java来说,反射就是操作类的Class对象来让我们可以在程序运行时发现和使用类的所有信息。当然如果没有了解Java的类的加载,可能看到这里会有个疑惑,直接New这个对象不就完了,为什么还需要反射?
那么首先我们都知道Java具有平台无关性,而之所以具有平台无关性,是因为Java会使用Javac编译,生成对应类的字节码,也就是.class文件,然后使用对应不同平台的JVM进行解析,转换成特定平台的执行指令。也就是说,我们每使用一个类,Java就会产生一个对应的Class对象。而生成这个class对象,会使用不同的类加载器进行加载。
关于类加载器,建议另外再详细学习一下。这里就不赘述了。我们这里只需要知道,从.java到.class这个过程。而如果我们现在有一个需求,现在我有A类以及B类,我想根据运行进行加载不同的类,那么这个时候,是不是就需要用到反射了。
反射的具体使用
创建测试类Student
public class Student {
private String name;
private String sex;
private int age;
}创建Class对象
- 使用对象.getClass()获取
Student student=new Student();
Class class=student.getClass();- 根据类名.class来获取
Class class=Student.class;- 使用Class.forName()方法获取,其中参数必须为带包名的类路劲
Class class=Class.forName("com.scominfo.demo01.entity.Student");第一种方法已经实例化对象了,再用反射没必要。第三种方法需要导入包路劲,不太方便,所以一般我们使用第二种方法。
使用反射
当我们获取到Class对象之后,就可以开始对其进行操作了。
获取公有构造方法
使用Class类中的getConstructors()方法获取到所有Public的构造方法列表、
Class studentClass = Class.forName("com.scominfo.demo01.entity.Student");
Constructor[] constructors = studentClass.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}查看打印结果如下:
public com.scominfo.demo01.entity.Student(java.lang.String,java.lang.String,int)
public com.scominfo.demo01.entity.Student()获取全部构造方法
通过以上方法获取,我们发现没有找到private的构造函数。如果想要获取"public,protected,default,privite"的构造函数,则应该使用getDeclaredConstructors()方法。
Class studentClass = Class.forName("com.scominfo.demo01.entity.Student");
Constructor[] constructors = studentClass.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}查看打印结果如下:
private com.scominfo.demo01.entity.Student(java.lang.String)
public com.scominfo.demo01.entity.Student(java.lang.String,java.lang.String,int)
public com.scominfo.demo01.entity.Student()此时发现private的构造方法也一起出现了。
获得具体类型的构造方法
那么如果我们需要获取到具体的某一个构造,就需要通过传入构造方法的入参的类型。
Class studentClass = Class.forName("com.scominfo.demo01.entity.Student");
Constructor constructor = studentClass.getConstructor(null);
Constructor constructor1 = studentClass.getConstructor(String.class, String.class, int.class);
Constructor declaredConstructor = studentClass.getDeclaredConstructor(String.class);
System.out.println(constructor);
System.out.println(constructor1);
System.out.println(declaredConstructor);查看打印结果,发现确实按照构造方法的入参类型进行输出了。
public com.scominfo.demo01.entity.Student()
public com.scominfo.demo01.entity.Student(java.lang.String,java.lang.String,int)
private com.scominfo.demo01.entity.Student(java.lang.String)注意:如果填入了没有对应构造方法入参类型,则会出现NoSuchMethodException错误。
通过构造方法实例化类
在我们获得了Constructor对象后,可以通过其中的newInstance方法进行实例化对象。
Class studentClass = Class.forName("com.scominfo.demo01.entity.Student");
Constructor studentconstructor = studentClass.getConstructor(null);
Constructor studentconstructor1 = studentClass.getConstructor(String.class, String.class,int.class);
Student student = (Student)studentconstructor.newInstance();
Student student1=(Student)studentconstructor1.newInstance("ltp","男",25);
student.setAge(28);
System.out.println(student);
System.out.println(student1);查看打印信息:
Student{name='null', sex='null', age=28}
Student{name='ltp', sex='男', age=25}获取公有成员变量
首先我们需要将上述实体类进行修改,将年龄改为公有变量public。名字和性别依然为私有。如无特殊说明,以下代码默认已经进行修改。
Class studentClass = Class.forName("com.scominfo.demo01.entity.Student");
Field[] fields = studentClass.getFields();
for (Field field : fields) {
System.out.println(field);
}输出结果如下:
public int com.scominfo.demo01.entity.Student.age那么我们可以得知,Class对象.getFields()只能获取共有成员变量列表。
获取所有成员变量
获取所有成员变量类似于获取所有构造函数一样,使用getDeclaredFields()方法即可。
Class studentClass = Class.forName("com.scominfo.demo01.entity.Student");
Field[] fields = studentClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}输出结果如下:
private java.lang.String com.scominfo.demo01.entity.Student.name
private java.lang.String com.scominfo.demo01.entity.Student.sex
public int com.scominfo.demo01.entity.Student.age获取指定成员变量
获得指定的成员变量其实和上面的获取指定的构造方法是一样的,使用getDeclaredField("属性名")即可
Class studentClass = Class.forName("com.scominfo.demo01.entity.Student");
Field field = studentClass.getDeclaredField("name");
System.out.println(field);输出结果如下:
private java.lang.String com.scominfo.demo01.entity.Student.name为成员变量赋值
获得了Field的对象后,可以调用get()和set()方法对某个对象中的属性进行取值和赋值的操作。
Class studentClass = Class.forName("com.scominfo.demo01.entity.Student");
Field field = studentClass.getDeclaredField("name");
Student student = (Student) studentClass.newInstance();
field.setAccessible(true);//解除私有限定
field.set(student,"ltp");
System.out.println(field.get(student));打印信息如下:
ltp注意在对私有的成员变量进行赋值操作时,要解除私有限定,调用setAccessible()方法,赋值为true,如果不进行设置,会出现IllegalAccessException错误
获取成员方法
这里,新建一个如下的类
public class TestMethod {
private String name;
public void isMyname(String name){
System.out.println("My name is"+name);
}
private String returnMyName(){
return name;
}
}和之前一样,使用getDeclaredMethods()可以获得所有方法,使用getMethods()仅仅能获取到公有的方法。
Class methodClass = Class.forName("com.scominfo.demo01.entity.TestMethod");
Method[] declaredMethods = methodClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("所有方法"+declaredMethod);
}
Method[] methods = methodClass.getMethods();
for (Method method : methods) {
System.out.println("公有方法"+method);
}打印结果如下:
所有方法public void com.scominfo.demo01.entity.TestMethod.isMyname(java.lang.String)
所有方法private java.lang.String com.scominfo.demo01.entity.TestMethod.returnMyName()
公有方法public void com.scominfo.demo01.entity.TestMethod.isMyname(java.lang.String)getDeclaredMethods()方法会获得自身和实现的接口中所有的方法,但是不会获得继承来的方法,getMethods()方法会获得所有的无论是实现的接口还是继承来的public的方法
获取指定的方法
通过方法名和方法中的参数类型就可以获得指定的方法
Class methodClass = Class.forName("com.scominfo.demo01.entity.TestMethod");
Method returnMyName = methodClass.getDeclaredMethod("returnMyName", null);
System.out.println(returnMyName);打印如下:
private java.lang.String com.scominfo.demo01.entity.TestMethod.returnMyName()使用指定方法
使用Class的invoke方法即可调用具体方法,但是要注意私有方法需要通过setAccessible(true)解除私有限定才能调用。
Class methodClass = Class.forName("com.scominfo.demo01.entity.TestMethod");
Method isMyname = methodClass.getDeclaredMethod("isMyname", String.class);
TestMethod testMethod = (TestMethod) methodClass.newInstance();
isMyname.setAccessible(true);//解决私有限定
isMyname.invoke(testMethod, "ltp");具体案例:通过反射将Map类转为对象
实体类依旧使用之前的student类。map转bean有很多工具类都可以使用,但是我们可以自己通过反射来手写一个。
void test005() throws Exception {
Map<String,Object> map=new HashMap<>();{
{
map.put("name","ltp");
map.put("age",20);
map.put("sex","男");
map.put("sss","xxx");
}
}
Student student = mapToBean(map, Student.class);
System.out.println(student.toString());
}
public static <T> T mapToBean(Map<String,Object> map,Class<T> clazz) throws Exception{
T obj=clazz.newInstance();
if(map !=null && map.size() > 0){
for (Map.Entry<String, Object> entry : map.entrySet()) {//循环获取到键值对
String propertyName = entry.getKey();
Object value = entry.getValue();//类型不确定
//这里我们获取到了属性名和属性的值,但是我们并不确定属性值的类型。
//所以,我们需要先获取到他的变量对象,也就是Filed
//但是我们需要注意,我们收到的map内,可能还含有其他的字段,而这些字段是不能转为bean的。所以需要排除掉。
Field field = getClassField(clazz, propertyName);
if(field == null){
continue;
}
//按照上面教程,我们是不是可以直接使用field.set就可以给变量赋值了?但是,这里要注意的是,这样可能会产生类型转换错误
//这是因为在使用反射获取或者修改一个变量的值时,编译器不会进行自动装/拆箱。所以我们需要换个思路,使用get,int方法来复制
//还有就是变量类型的问题,当然这可以通过判断变量类型来判断是否添加setAccessible来解决。
//综上所述,使用set方法是最好的选择
String setMethodName = "set"
+ propertyName.substring(0, 1).toUpperCase()
+ propertyName.substring(1);
//获取方法
Method setMethod = clazz.getMethod(setMethodName, field.getType());
//调用此方法赋值,这里又要注意,赋值,应该对应所需要填入的value类型,我们现在的value是Object的,所以需要转换
value = convertValType(value, field.getType());
setMethod.invoke(obj,value);
}
}
return obj;
}
private static Field getClassField(Class<?> clazz, String fieldName){
//通过clazz获取到所有变量
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
if(field.getName().equals(fieldName)){
return field;
}
}
//这里应该再查找一次父类,因为可能这个类集成了某父类
Class<?> superClass = clazz.getSuperclass();
//简单的递归一下
if (superClass != null) {
return getClassField(superClass, fieldName);
}
return null;
}
private static Object convertValType(Object value, Class<?> fieldTypeClass) {
Object retVal = null;
if (Long.class.getName().equals(fieldTypeClass.getName())
|| long.class.getName().equals(fieldTypeClass.getName())) {
retVal = Long.parseLong(value.toString());
} else if (Integer.class.getName().equals(fieldTypeClass.getName())
|| int.class.getName().equals(fieldTypeClass.getName())) {
retVal = Integer.parseInt(value.toString());
} else if (Float.class.getName().equals(fieldTypeClass.getName())
|| float.class.getName().equals(fieldTypeClass.getName())) {
retVal = Float.parseFloat(value.toString());
} else if (Double.class.getName().equals(fieldTypeClass.getName())
|| double.class.getName().equals(fieldTypeClass.getName())) {
retVal = Double.parseDouble(value.toString());
} else {
retVal = value;
}
return retVal;
}总结
在使用反射修改某个对象的成员变量前你要明白,这样做会造成一定程度的性能开销,因为在反射时这样的操作需要引发许多额外操作,比如验证访问权限等。只在特殊情况下这么做。此文的目的是理解,而不是让我们在程序中大面积使用它。

浙公网安备 33010602011771号