【Java基础】反射机制及应用

反射机制及应用

什么是反射

在程序运行状态中,对于任意一个类或对象,都能够获取到这个类的所有属性和方法(包括私有属性和方法),这种动态获取信息以及动态调用对象方法的功能就称为反射机制。简单来讲,通过反射,类对我们是完全透明的,想要获取任何东西都可以。

Class对象

我们创建的每一个类也都是对象,即类本身是java.lang.Class类的实例对象。这个实例对象称之为类对象,也就是Class对象。

  • Class类的实例对象表示正在运行的Java应用程序中的类和接口,也就是JVM中的每一个实例,每一个类都有唯一的Class对象
  • Class类没有公共的构造方法,Class对象是在加载类时由Java虚拟机自动构造的
  • Class 对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法

获取Class对象的三种方式

  1. 可以在源代码阶段进行获取。Class.forName(“全类名”)。将字节码文件加载进内存,返回Class对象,多用于配置文件,将类名定义的配置文件中。
  2. Class类对象阶段获取:类名.class。此方式多用于参数的传递
  3. Runtime阶段获取:new 类名().getClass()。该方法是定义在Object类中的(所有类均继承自Object)方法,因此所有的类都会继承该方法。多用于对象获取字节码的方式。

获取Class对象案例

public class GetClass {

    @Test
    public void classForName() throws ClassNotFoundException {
        // 方式一 Class.forName("全类名")
        final Class<?> clazz1 = Class.forName("cn.wujiwen.basic.reflect.ExampleClass");
        System.out.println("class1:" + clazz1);
    }

    @Test
    public void nameClass(){
        // 方式二 类名.class
        final Class<ExampleClass> clazz2 = ExampleClass.class;
        System.out.println("class2:" + clazz2);
    }

    @Test
    public void ObjectGetClass(){
        // 方式三 实例对象.getClass()
        ExampleClass exampleClass = new ExampleClass();
        Class<?> clazz3 = exampleClass.getClass();
        System.out.println("class3:" + clazz3);
    }

    // 同一个节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
    @Test
    public void all() throws ClassNotFoundException {
        Assertions.assertSame(Class.forName("cn.wujiwen.basic.reflect.ExampleClass") , ExampleClass.class);
    }
}

Class对象的功能

这里将简单记录三种常用的功能,更多方法可完整阅读class源码

1、获取成员变量

//获取所有public修饰的成员变量
Field[] getFields() 
//获取指定名称的public修饰的成员变量         
Field getField(String name)  
//获取所有的成员变量,不考虑修饰符
Field[] getDeclaredFields() 
//获取指定的成员变量,不考虑修饰符 
Field getDeclaredField(String name)  

2、获取构造方法

//获取所有public修饰的构造函数
Constructor<?>[] getConstructors()
//获取指定的public修饰的构造函数
Constructor<T> getConstructor(类<?>... parameterTypes)
//获取所有的构造函数,不考虑修饰符
Constructor<?>[] getDeclaredConstructors() 
//获取指定的构造函数,不考虑修饰符 
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)  //获取指定的构造函数,不考虑修饰符

3、获取成员方法

//获取所有public修饰的成员方法
Method[] getMethods()   
//获取指定名称的public修饰的成员方法        
Method getMethod(String name, 类<?>... parameterTypes) 
//获取所有的成员方法,不考虑修饰符
Method[] getDeclaredMethods()  
//获取指定名称的成员方法,不考虑修饰符
Method getDeclaredMethod(String name, 类<?>... parameterTypes) 

下面我们一个一个类测试一下,先准备以上功能方法所需要用到的类、属性信息

public class ExampleClass {
    // public修饰的属性
    public String publicField;
    // private 修饰的属性
    private String privateField;
    // 默认
    String defaultField;
    // protected 修饰的户型
    protected String protectedField;

    // protected 无参数构造,与public无参数构造冲突,会发生覆盖操作
    protected ExampleClass(){

    }
    // public 一个参数的构造函数
    public ExampleClass(String privateField){
        this.privateField = privateField;
    }
    
    public void publicMethod(){

    }

    private void privateMethod(){

    }

    protected void protectedMethod(){

    }

    void defaultMethod(){

    }
		
		public void noParamsMethod(){
        System.out.println("this is noParamsMethod");
    }
    
    public void paramsMethod(String params){
        System.out.println("this is paramsMethod,params is " + params);
    }
}

下面我们将测试一下每一个功能的使用和答应信息

获取成员变量

@Test
public void getField() throws NoSuchFieldException {
    Class<ExampleClass> exampleClassClass = ExampleClass.class;
    
		Field[] fields = exampleClassClass.getFields();
    System.out.println(Arrays.asList(fields));
		// 返回了仅有的一个public属性信息
		// [public java.lang.String cn.wujiwen.basic.reflect.ExampleClass.publicField]

    Field publicField = exampleClassClass.getField("publicField");
    System.out.println(publicField);
		// 能够获取到,如果设置成其他的属性参数,将会报错
		// public java.lang.String cn.wujiwen.basic.reflect.ExampleClass.publicField

    Field[] declaredFields = exampleClassClass.getDeclaredFields();
    System.out.println(Arrays.asList(declaredFields));
		// 返回了所有的属性信息,不考虑修饰符信息
		// [public java.lang.String cn.wujiwen.basic.reflect.ExampleClass.publicField, private java.lang.String cn.wujiwen.basic.reflect.ExampleClass.privateField, java.lang.String cn.wujiwen.basic.reflect.ExampleClass.defaultField, protected java.lang.String cn.wujiwen.basic.reflect.ExampleClass.protectedField]

    Field privateField = exampleClassClass.getDeclaredField("privateField");
    System.out.println(Arrays.asList(privateField));
		// private java.lang.String cn.wujiwen.basic.reflect.ExampleClass.privateField
}

获取构造函数

@Test
public void getConstructor() throws NoSuchMethodException {
    Class<ExampleClass> exampleClassClass = ExampleClass.class;
    
    Constructor<?>[] constructors = exampleClassClass.getConstructors();
    System.out.println(Arrays.toString(constructors));
    // [public cn.wujiwen.basic.reflect.ExampleClass(java.lang.String)]

    Constructor<ExampleClass> constructor = exampleClassClass.getConstructor(String.class);
    System.out.println(constructor);
		// public cn.wujiwen.basic.reflect.ExampleClass(java.lang.String)

    Constructor<?>[] declaredConstructors = exampleClassClass.getDeclaredConstructors();
    System.out.println(Arrays.toString(declaredConstructors));
    // [public cn.wujiwen.basic.reflect.ExampleClass(java.lang.String), protected cn.wujiwen.basic.reflect.ExampleClass()]
    
		Constructor<ExampleClass> declaredConstructor = exampleClassClass.getDeclaredConstructor(null);
    System.out.println(declaredConstructor);
		// protected cn.wujiwen.basic.reflect.ExampleClass()
		
}

获取成员方法

@Test
public void getMethod() throws Exception {
    Class<ExampleClass> exampleClassClass = ExampleClass.class;
    Method[] methods = exampleClassClass.getMethods();
    System.out.println(Arrays.toString(methods));
		// 返回所有的public 包括Object类中的wait等方法信息
		// [public void cn.wujiwen.basic.reflect.ExampleClass.publicMethod(), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
    
		Method publicMethod = exampleClassClass.getMethod("publicMethod");
    System.out.println(publicMethod);
		// public void cn.wujiwen.basic.reflect.ExampleClass.publicMethod()
		
    Method[] declaredMethods = exampleClassClass.getDeclaredMethods();
    System.out.println(Arrays.toString(declaredMethods));
		// 只返回本类中的所有方法
		// [private void cn.wujiwen.basic.reflect.ExampleClass.privateMethod(), public void cn.wujiwen.basic.reflect.ExampleClass.publicMethod(), protected void cn.wujiwen.basic.reflect.ExampleClass.protectedMethod(), void cn.wujiwen.basic.reflect.ExampleClass.defaultMethod()]

    Field protectedField = exampleClassClass.getDeclaredField("protectedField");
    System.out.println(protectedField);
		// protected java.lang.String cn.wujiwen.basic.reflect.ExampleClass.protectedField

    Method enclosingMethod = exampleClassClass.getEnclosingMethod();
    System.out.println(enclosingMethod);
		// null 没有内部匿名方法 orz
}

Field对象的功能

常用的功能通常有获取属性值,赋予新值等功能

@Test
public void modifyField() throws Exception {
    ExampleClass exampleClass = new ExampleClass();
    Class<? extends ExampleClass> classClass = exampleClass.getClass();
    Field publicField = classClass.getField("publicField");
    Object publicFieldValue = publicField.get(exampleClass);
    System.out.println(publicFieldValue);

    Field privateField = classClass.getDeclaredField("privateField");
    // // 忽略访问权限的检查,暴力反射
    privateField.setAccessible(true);
    // 获取值
    Object privateFieldVale = privateField.get(exampleClass);
    System.out.println(privateFieldVale);
    // 赋新值
    privateField.set(exampleClass,"newPrivateValue");
    Object newValue = privateField.get(exampleClass);
    System.out.println(newValue);
}

Method对象的功能

  • 执行方法 Object invoke(Object obj,Object... args);
  • 获取参数 getParameters()
@Test
public void methodFun() throws Exception {
    ExampleClass exampleClass = new ExampleClass();
    Class<? extends ExampleClass> classClass = exampleClass.getClass();
    Method noParamsMethod = classClass.getMethod("noParamsMethod");
    // 执行方法
    noParamsMethod.invoke(exampleClass);

    Method paramsMethod = classClass.getMethod("paramsMethod", String.class);
    // 获取参数
    Parameter[] parameters = paramsMethod.getParameters();
    System.out.println(Arrays.toString(parameters));
    // 执行方法
    paramsMethod.invoke(exampleClass,"这是参数");
}

扩展

在整理Field,Method,Constructor的过程中,我们发现都继承自AccessibleObject类,上面提到的setAccessible(true);方法就是该类提供的一个功能。下面还介绍一种框架中非常常用的注解功能,在反射中我们可以获取到上述三个对象的注解,进而完成更多的自定义功能

首先我们在ExampleClass添加jdk中自带的注解信息并尝试通过反射的方式进行注解信息的获取

@Deprecated
public class ExampleClass {
    // public修饰的属性
    @Deprecated
    public String publicField;
    @Deprecated
    public void publicMethod(){

    }
}

我们分别在类名属性及方法上添加类一个常见的过期注解

@Test
public void getAnnotation() throws Exception {
    ExampleClass exampleClass = new ExampleClass();
    Class<? extends ExampleClass> classClass = exampleClass.getClass();
    Annotation[] classAnnotations = classClass.getDeclaredAnnotations();
    System.out.println(Arrays.toString(classAnnotations));

    Field publicField = classClass.getField("publicField");
    Annotation[] fieldAnnotations = publicField.getAnnotations();
    System.out.println(Arrays.toString(fieldAnnotations));

    Method publicMethod = classClass.getMethod("publicMethod");
    Deprecated annotation = publicMethod.getAnnotation(Deprecated.class);
    System.out.println(annotation);
}

这样我们就可以获取到相关对象上的注解信息。

实战应用

上面我们结合案例列举了很多反射中常用的功能和用法,下面我们将结合一个简单的功能需求将上面的功能应用在开发实战中,由于是简单应用,不做实际开发参考。

功能描述

现在我们有一组已经解析完整的数据(通过excel读取或者xml方式等等),需要根据要求,组装到对应的对象中,进行后期的应用,比如:我们有几组统计数据,一组是学校的学生信息数据,另一组是学校的教师信息的数据,已经通过解析工具从三方表格中解析出来并封装到了一个数组集合中,数组索引所对应的数据已知,如index=0的代表姓名

我们先将数据准备好

List<List<Object>> teachers = new ArrayList<>();
List<List<Object>> students = new ArrayList<>();

@BeforeEach
public void data(){
    List<Object> teacher1 = new ArrayList<>();
    teacher1.add("张老师");
    teacher1.add(20);
    teacher1.add("13011111111");
    teacher1.add("语文");

    List<Object> teacher2 = new ArrayList<>();
    teacher2.add("李老师");
    teacher2.add(40);
    teacher2.add("13811111111");
    teacher2.add("数学");

    teachers.add(teacher1);
    teachers.add(teacher2);

    List<Object> student1 = new ArrayList<>();
    student1.add("李华");
    student1.add(10);
    student1.add("三年级一班");

    List<Object> student2 = new ArrayList<>();
    student2.add("赵铁柱");
    student2.add(11);
    student2.add("四年级二班");

    students.add(student1);
    students.add(student2);
}
[[张老师, 20, 13011111111, 语文], [李老师, 40, 13811111111, 数学]]
[[李华, 10, 三年级一班], [赵铁柱, 11, 四年级二班]]

现在有两个类,分别是StudentTeacher类需要接收相应的数据信息查

public class Student {
    /**
     * 学生姓名
     */
    private String studentName;
    /**
     * 班级名称
     */
    private String className;
    /**
     * 年龄
     */
    private Integer age;
		
		// setter getter ...
}

public class Teacher {
    private String name;
    private String mobile;
    private String subject;
    private Integer age;
		// setter getter ...
}

下面我们将通过反射的方式将上面的两组输入放入到对应的对象中,并准确对应在各自的属性中

我们可以通过自定义注解的方式,将注解内容作用到每一个字段上,并标记好该字段在数组中的具体索引值,然后赋值给该字段即可。

自定义一个字段索引注解

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {FIELD})
public @interface FieldIndex {
    /**
     * 对应的索引
     * @return
     */
    int index();
}

改造Student 和Teacher类

public class Student {
    /**
     * 学生姓名
     */
    @FieldIndex(index = 0)
    private String studentName;
    /**
     * 班级名称
     */
    @FieldIndex(index = 2)
    private String className;
    /**
     * 年龄
     */
    @FieldIndex(index = 1)
    private Integer age;
}

public class Teacher {
    @FieldIndex(index = 0)
    private String name;
    @FieldIndex(index = 2)
    private String mobile;
    @FieldIndex(index = 3)
    private String subject;
    @FieldIndex(index = 1)
    private Integer age;
}

定义一个工具方法

/**
 *
 * @param t 需要绑定的类
 * @param data 处理的数据
 * @param <T>
 * @return
 */
public <T> void bind(T t,List<Object> data){
    Class<?> clazz = t.getClass();
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        // 获取字段的注解
        FieldIndex annotation = field.getAnnotation(FieldIndex.class);
        // 有效注解
        if (annotation != null && annotation.index() >= 0){
            // 获取注解中的索引值
            int index = annotation.index();
            // 获取data中的数据
            Object value = data.get(index);
            // 忽略检查
            field.setAccessible(true);
            // 复制给该字段
            try {
                field.set(t,value);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

测试

@Test
public void dataBind(){
    List<Teacher> teacherList = new ArrayList<>();
    for (List<Object> teacherArr : teachers) {
        Teacher teacher = new Teacher();
        bind(teacher,teacherArr);
        teacherList.add(teacher);
    }
    List<Student> studentList = new ArrayList<>();
    for (List<Object> studentArr : students) {
        Student student = new Student();
        bind(student,studentArr);
        studentList.add(student);
    }
    System.out.println(teacherList);
    System.out.println(studentList);

}

这样我们就完成了一个简单的数据绑定了,类似功能的扩展还有很多,比如在次基础上进行数据的校验。在我们的实际开发过程中也有很多这样的处理方式,利用反射机制能大大方便我们进行开发。

posted @ 2021-04-09 10:05  黑米面包派  阅读(526)  评论(2编辑  收藏  举报