Java高级语法之反射
阅读原文时间:2022年01月28日阅读:2

Java高级语法之反射

java.lang包提供java语言程序设计的基础类,在lang包下存在一个子包:reflect,与反射相关的APIs均在此处;

官方对reflect包的介绍如下:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.

The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

java.lang.reflect官方介绍

简单来说,反射机制就像类对照平静的湖面,湖面映射出类的字段、方法、构造函数等信息;反射机制不仅可以看到类信息,还能针对字段、方法等做出相应的操作。

为方便解释说明,首先创建一个实体类,用于测试使用

package cn.byuan.entity;

/**
 * 学生实体类
 *
 * @author byuan
 * @date 2022-01-25
 */
public class Student {

    /**
     * 学生学号, 公共变量, 默认值: defaultStudentNo
     * */
    public String studentNo = "defaultStudentNo";

    /**
     * 学生姓名, 公共变量, 默认值: defaultStudentName
     * */
    public String studentName = "defaultStudentName";

    /**
     * 学生性别, 私有变量, 默认值: defaultStudentSex
     * */
    private String studentSex = "defaultStudentSex";

    /**
     * 学生年龄, 私有变量, 默认值: 0
     * */
    private Integer studentAge = 0;

    /**
     * 公有无参构造方法
     * */
    public Student() {

    }

    /**
     * 公有满参构造方法
     * */
    public Student(String studentNo, String studentName, String studentSex, Integer studentAge) {
        this.studentNo = studentNo;
        this.studentName = studentName;
        this.studentSex = studentSex;
        this.studentAge = studentAge;
    }

    /**
     * 私有构造方法
     * */
    private Student(String studentSex, Integer studentAge) {
        this.studentSex = studentSex;
        this.studentAge = studentAge;
    }

    public String getStudentNo() {
        return studentNo;
    }

    public Student setStudentNo(String studentNo) {
        this.studentNo = studentNo;
        return this;
    }

    public String getStudentName() {
        return studentName;
    }

    public Student setStudentName(String studentName) {
        this.studentName = studentName;
        return this;
    }

    public String getStudentSex() {
        return studentSex;
    }

    public Student setStudentSex(String studentSex) {
        this.studentSex = studentSex;
        return this;
    }

    public Integer getStudentAge() {
        return studentAge;
    }

    public Student setStudentAge(Integer studentAge) {
        this.studentAge = studentAge;
        return this;
    }

    @Override
    public String toString() {
        return "Student{" +
                "studentNo = " + this.studentNo + ", " +
                "studentName = " + this.studentName + ", " +
                "studentSex = " + this.studentSex + ", " +
                "studentAge = " + this.studentAge +"}";
    }

    /**
     * 学生类说话方法
     * */
    private String speak(String message) {
        return this.studentName + " : " + message;
    }

}

在了解反射前,先来梳理下一个类(Class)本身中包含了哪些内容。

  1. 字段,字段又由修饰符、字段类型、字段名称、对应值等部分组成
  2. 构造方法,构造方法可简单分为无参与有参两大类
  3. 非构造方法,又称普通方法,方法由修饰符、返回值类型、方法名、形参表、方法体等部分构成

本文所论述的反射中几个重要类及其对应方法正基于以上内容

Class类

Class类代表了类本身,类本身包含字段,构造方法,非构造方法等内容,因此使用反射的第一步就是获取对象所对应的Class类。

仅就使用反射而言,我们需着重了解Class类的获取方式,下面给出实例

Class类实例

package cn.byuan.example;

import cn.byuan.entity.Student;

/**
 * 获取 Class 的几种方式
 *
 * @author byuan
 * @date 2022-01-25
 */
public class GetClassExample {

    public static void main(String[] args) throws ClassNotFoundException {

        // 获取 class 方式一: 通过类的全路径字符串获取 Class 对象
        Class getClassExample1 = Class.forName("cn.byuan.entity.Student");

        // 获取 class 方式二: 通过类名直接获取
        Class getClassExample2 = Student.class;

        // 获取 class 方式三: 通过已创建的对象获取对应 Class
        Student student1 = new Student();
        Class getClassExample3 = student1.getClass();

    }

}

Field类的获取与常用属性

Class类为我们提供了两个方法用以获取Field类:

  1. getDeclaredFields: 获取所有声明的字段(包括公有字段和私有字段)
  2. getFields: 仅可获取公有字段

Field类代表了类中的属性字段,类中属性字段可分为两种,公有字段(public)与私有字段(private);

每个字段又具有四个属性:修饰符,字段类型,字段名称,对应值;Field也自然提供了对应方法对这四种属性进行获取:

  1. getModifiers():获取字段修饰符相加值,想要获取明确标识需要通过Modifier常量的toString方法对相加值进行解码
  2. getType():获取字段类型
  3. getName():获取字段名称
  4. get(Object):获取字段对应值

下面给出实例:

Field类实例

package cn.byuan.example;

import cn.byuan.entity.Student;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * 获取类字段的几种方式
 *
 * @author byuan
 * @date 2021-01-25
 */
public class GetFieldExample {

    public static void main(String[] args) throws IllegalAccessException {

        Class studentClass = Student.class;

        // 获取类字段的两个方法: getDeclaredFields, getFields

        // 1. getDeclaredFields: 获取所有声明的字段(包括公有字段和私有字段)
        Field[] declaredFieldArray = studentClass.getDeclaredFields();
        printFieldInformation(declaredFieldArray);

        // 2. getFields: 仅可获取公有字段
        Field[] fieldArray = studentClass.getFields();
        printFieldInformation(fieldArray);

        // 获取字段对应值
        Student student = new Student()
                .setStudentSex("女")
                .setStudentAge(18);
        printFieldValue(student);

    }

    /**
     * 打印类字段信息
     *
     * @param fieldArray 类字段对象列表
     * */
    public static void printFieldInformation(Field[] fieldArray) {

        for (Field fieldPart : fieldArray) {

            System.out.println("直接打印类字段对象: " + fieldPart);

            // 获取字段修饰符
            String fieldModifier = Modifier.toString(fieldPart.getModifiers());

            // 获取字段类型
            String fieldType = fieldPart.getType().getName();

            // 获取字段名称
            String fieldName = fieldPart.getName();

            System.out.println(fieldModifier + " " + fieldType + " " + fieldName);

        }

        System.out.println();

    }

    /**
     * 打印类字段属性
     *
     * @param t 泛型对象
     * */
    private static <T> void printFieldValue(T t) throws IllegalAccessException {
        Field[] fieldValueArray = t
                .getClass()
                .getDeclaredFields();

        for (Field fieldValuePart : fieldValueArray) {
            // 对于有可能存在的 private 字段取消语言访问检查
            fieldValuePart.setAccessible(true);

            // 字段名称
            String filedName = fieldValuePart.getName();

            // 字段对应值
            String fieldValue = fieldValuePart.get(t).toString();

            System.out.println(filedName + " = " + fieldValue);

        }

    }

}

运行截图

Constructor类获取与常用属性

Class类为我们提供了两个方法用以获取Constructor类:

  1. getDeclaredConstructors():获取所有构造方法
  2. getConstructors():仅可获取公有构造方法

对于构造器,可简单将其分为两大类,无参构造器与带参构造器,构造器也是方法的一种,因此对于任意一构造器都由修饰符,方法名,形参表,方法体四部分组成;Constructor自然也为其组成部分提供了对应方法:

  1. 与字段类似,Constructors同样提供了getModifiers()方法:获取构造器修饰符相加值,想要获取明确标识需要通过Modifier常量的toString方法进行解码
  2. getName():获取构造器名称
  3. getParameterTypes():获取构造器参数列表

下面给出实例

Constructor类实例

package cn.byuan.example;

import cn.byuan.entity.Student;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

/**
 * 获取构造方法的几种方式
 *
 * @author byuan
 * @date 2022-01-25
 */
public class GetConstructorsExample {

    public static void main(String[] args) {

        Class studentClass = Student.class;

        // 获取类构造器的两个方法: getDeclaredConstructors, getConstructors

        // 1. getDeclaredConstructors: 获取所有构造方法
        Constructor[] declaredConstructorArray = studentClass.getDeclaredConstructors();
        printConstructorInformation(declaredConstructorArray);

        // 2. getConstructors, 仅可获取公有构造方法
        Constructor[] constructorArray = studentClass.getConstructors();
        printConstructorInformation(constructorArray);

    }

    /**
     * 打印构造器信息
     *
     * @param constructorArray 构造器对象列表
     * */
    public static void printConstructorInformation(Constructor[] constructorArray) {

        for (Constructor constructorPart : constructorArray) {

            System.out.println("直接打印构造器对象: " + constructorPart);

            // 获取构造器修饰符
            String constructorModifier = Modifier.toString(constructorPart.getModifiers());

            // 获取构造器名称
            String constructorName = constructorPart.getName();

            // 获取构造器参数列表
            Class[] constructorParameterArray = constructorPart.getParameterTypes();

            // 打印构造器参数列表
            System.out.print(constructorModifier + " " + constructorName + "(");
            for (Class constructorParameterPart : constructorParameterArray) {
                System.out.print(constructorParameterPart.getName() + " ");
            }
            System.out.println(")");

        }

        System.out.println();

    }

}

运行截图

利用Constructor类实例化对象

一般而言,我们关心的不只是获取到对象构造器的具体信息,更重要的是如何利用反射实例化对象,针对对象实例化,Constructor提供了两种类型的方法:

  1. getConstructor(Class… parameterTypes):获取指定形参表的构造方法,可借助其获取无参/指定参数的构造方法;

  2. newInstance(Object … initargs):通过形参表传递实例化对象参数,与getConstructor配合使用;

    注意:Class也存在newInstance()方法,但仅能用来调用类的无参构造器

Constructor实例化对象实例

package cn.byuan.example;

import cn.byuan.entity.Student;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 构造器调用实例
 *
 * @author byuan
 * @date 2022-01-26
 */
public class ConstructorsInvokeExample {

    public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {

        Class studentClass = Student.class;

        // Class 类 的 newInstance 方法
        studentClass.newInstance();

        // 1. 调用公有无参构造器
        Object object1 = studentClass
                .getConstructor()
                .newInstance();
        System.out.println(object1.getClass());

        // 2. 调用公有满参构造器
        Constructor studentConstructorFull = studentClass
                .getConstructor(String.class, String.class, String.class, Integer.class);

        Object object2 = studentConstructorFull
                .newInstance("2022001", "赵一", "男", 18);
        System.out.println(object2);

        // 3. 调用私有构造器
        Constructor studentConstructorPrivate = studentClass
                .getDeclaredConstructor(String.class, Integer.class);

        // 私有构造器需将 accessible 设置为 true, 取消语言访问检查
        studentConstructorPrivate.setAccessible(true);
        Object object3 = studentConstructorPrivate
                .newInstance("女", 19);
        System.out.println(object3);

    }

}

运行截图

Method类的获取与常用属性

Class类为我们提供了两个方法用以获取Method类:

  1. getDeclaredMethods():获取所有非构造方法
  2. getMethods():仅可获取公有非构造方法

对于任意方法都可分为修饰符,方法名,形参表,方法体四部分;Method为其组成部分提供了对应方法:

  1. getModifiers():获取方法修饰符相加值,想要获取明确标识需要通过Modifier常量的toString方法进行解码
  2. getName():获取方法名称
  3. getParameterTypes():获取方法形参表

Method类实例

package cn.byuan.example;

import cn.byuan.entity.Student;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * 获取非构造方法的几种方式
 *
 * @author byuan
 * @date 2022-01-26
 */
public class GetMethodExample {

    public static void main(String[] args) {

        Class studentClass = Student.class;

        // 获取非构造方法的两个方法: getDeclaredMethods, getMethods

        // 1. getDeclaredMethods: 获取所有非构造方法
        Method[] declaredMethodArray = studentClass.getDeclaredMethods();
        printMethodInformation(declaredMethodArray);

        // 2. getMethods, 仅可获取公有非构造方法
        Method[] methodArray = studentClass.getMethods();
        printMethodInformation(methodArray);

    }

    /**
     * 打印非构造器方法信息
     *
     * @param methodArray 构造器对象列表
     * */
    public static void printMethodInformation(Method[] methodArray) {

        for (Method methodPart : methodArray) {

            System.out.println("直接打印非构造方法对象: " + methodArray);

            // 获取非构造器方法修饰符
            String methodModifier = Modifier.toString(methodPart.getModifiers());

            // 获取非构造器方法名称
            String methodName = methodPart.getName();

            String methodReturnType = methodPart.getReturnType().getName();

            // 获取非构造方法参数列表
            Class[] constructorParameterArray = methodPart.getParameterTypes();

            // 打印非构造方法参数列表
            System.out.print(methodModifier + " " + methodReturnType + " " + methodName + "(");
            for (Class methodParameterPart : constructorParameterArray) {
                System.out.print(methodParameterPart.getName() + " ");
            }
            System.out.println(")");

        }

        System.out.println();

    }

}

运行截图

利用Method调用非构造方法

与利用Constructor实例化对象相似,Method同样需要两个方法用以调用非构造方法

  1. getDeclaredMethod(方法名, 形参表数组): 获取所有构造方法
  2. invoke(对象实例, 参数数组):方法调用

Method调用非构造方法实例

package cn.byuan.example;

import cn.byuan.entity.Student;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 非构造器方法调用实例
 *
 * @author byuan
 * @date 2022-01-26
 */
public class MethodInvokeExample {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {

        Class studentClass = Student.class;

        // 对于私有的非构造方法, 需要使用 getDeclaredMethod 进行获取
        // getDeclaredMethod(方法名, 形参表数组)
        Method studentSpeakMethod = studentClass.getDeclaredMethod("speak", new Class[]{String.class});

        // 取消语言访问检查
        studentSpeakMethod.setAccessible(true);

        // invoke(对象实例, 参数数组)
        Object object = studentSpeakMethod.invoke(studentClass.newInstance(), "Hello, world");

        System.out.println(object);

    }

}

运行截图

实例:利用反射机制编写对象拷贝工具类

在实际项目中,我们经常会遇到POJO与VO等类型对象进行相互转换的情况,如果直接进行转换则会使用大量的样板代码,为消除这样的代码,我们可以写一个简单的对象拷贝工具类进行解决;

业务分析

通过反射获取源对象Field数组,并利用Field类提供的set/get方法实现同名属性的拷贝;

创建两个具有相同属性的对象:Student与StudentOut

Student类
package cn.byuan.entity;

/**
 * 学生实体类
 *
 * @author byuan
 * @date 2022-01-25
 */
public class Student {

    /**
     * 学生学号, 公共变量, 默认值: defaultStudentNo
     * */
    public String studentNo = "defaultStudentNo";

    /**
     * 学生姓名, 公共变量, 默认值: defaultStudentName
     * */
    public String studentName = "defaultStudentName";

    /**
     * 学生性别, 私有变量, 默认值: defaultStudentSex
     * */
    private String studentSex = "defaultStudentSex";

    /**
     * 学生年龄, 私有变量, 默认值: 0
     * */
    private Integer studentAge = 0;

    /**
     * 公有无参构造方法
     * */
    public Student() {

    }

    /**
     * 公有满参构造方法
     * */
    public Student(String studentNo, String studentName, String studentSex, Integer studentAge) {
        this.studentNo = studentNo;
        this.studentName = studentName;
        this.studentSex = studentSex;
        this.studentAge = studentAge;
    }

    /**
     * 私有构造方法
     * */
    private Student(String studentSex, Integer studentAge) {
        this.studentSex = studentSex;
        this.studentAge = studentAge;
    }

    public String getStudentNo() {
        return studentNo;
    }

    public Student setStudentNo(String studentNo) {
        this.studentNo = studentNo;
        return this;
    }

    public String getStudentName() {
        return studentName;
    }

    public Student setStudentName(String studentName) {
        this.studentName = studentName;
        return this;
    }

    public String getStudentSex() {
        return studentSex;
    }

    public Student setStudentSex(String studentSex) {
        this.studentSex = studentSex;
        return this;
    }

    public Integer getStudentAge() {
        return studentAge;
    }

    public Student setStudentAge(Integer studentAge) {
        this.studentAge = studentAge;
        return this;
    }

    @Override
    public String toString() {
        return "Student{" +
                "studentNo = " + this.studentNo + ", " +
                "studentName = " + this.studentName + ", " +
                "studentSex = " + this.studentSex + ", " +
                "studentAge = " + this.studentAge +"}";
    }

    /**
     * 学生类说话方法
     * */
    private String speak(String message) {
        return this.studentName + " : " + message;
    }

}
StudentOut类
package cn.byuan.api.out;

import cn.byuan.entity.Student;

/**
 * 学生类出参
 *
 * @author byuan
 * @date 2022-01-26
 */
public class StudentOut {
    /**
     * 学生学号, 公共变量
     * */
    private String studentNo;

    /**
     * 学生姓名, 公共变量
     * */
    private String studentName;

    /**
     * 学生性别, 私有变量
     * */
    private String studentSex;

    /**
     * 学生年龄, 私有变量
     * */
    private Integer studentAge;

    public String getStudentNo() {
        return studentNo;
    }

    public StudentOut setStudentNo(String studentNo) {
        this.studentNo = studentNo;
        return this;
    }

    public String getStudentName() {
        return studentName;
    }

    public StudentOut setStudentName(String studentName) {
        this.studentName = studentName;
        return this;
    }

    public String getStudentSex() {
        return studentSex;
    }

    public StudentOut setStudentSex(String studentSex) {
        this.studentSex = studentSex;
        return this;
    }

    public Integer getStudentAge() {
        return studentAge;
    }

    public StudentOut setStudentAge(Integer studentAge) {
        this.studentAge = studentAge;
        return this;
    }

    @Override
    public String toString() {
        return "StudentOut{" +
                "studentNo = " + this.studentNo + ", " +
                "studentName = " + this.studentName + ", " +
                "studentSex = " + this.studentSex + ", " +
                "studentAge = " + this.studentAge +"}";
    }
}
编写工具类
package cn.byuan.util;

import cn.byuan.api.out.StudentOut;
import cn.byuan.entity.Student;

import java.lang.reflect.Field;

/**
 * 对象属性拷贝工具类
 *
 * @author byuan
 * @date 2022-01-26
 */
public class BeanUtil {

    /**
     * 对象拷贝工具
     *
     * @param sourceObject 源对象
     * @param destClass 目的对象对应 Class
     *
     * @return 拷贝完毕后对象
     * */
    public static <T> T copyObject(Object sourceObject, Class<T> destClass) {

        if (sourceObject == null) {
            return null;

        }

        try {

            T destObject = destClass.newInstance();

            copyField(sourceObject, destObject);

            return destObject;

        } catch (InstantiationException e) {
            e.printStackTrace();

        } catch (IllegalAccessException e) {
            e.printStackTrace();

        } catch (Exception e) {
            e.printStackTrace();

        }

        return null;

    }

    /**
     * 对象属性拷贝工具
     *
     * @param sourceObject 源对象
     * @param destObject 目的对象
     * */
    private static void copyField(Object sourceObject, Object destObject) {

        if (sourceObject == null || destObject == null) {
            return;
        }

        // 获取源对象所有字段
        Field[] sourceFieldArray = sourceObject.getClass().getDeclaredFields();
        for (Field sourceFieldPart : sourceFieldArray) {
            // 取消语言访问检查
            sourceFieldPart.setAccessible(true);

            String sourceFieldName = sourceFieldPart.getName();

            try {
                // 根据属性名称获取目标对象对应类字段
                Field destField = destObject
                        .getClass()
                        .getDeclaredField(sourceFieldName);

                destField.setAccessible(true);

                destField.set(destObject, sourceFieldPart.get(sourceObject));

            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {

        Student student = new Student("2022001", "赵一", "男", 18);

        StudentOut studentOut = copyObject(student, StudentOut.class);

        System.out.println(studentOut);

    }

}