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); } }

Java高级语法之反射的更多相关文章

  1. 从一知半解到揭晓Java高级语法—泛型

    目录 前言 探讨 泛型解决了什么问题? 扩展 引入泛型 什么是泛型? 泛型类 泛型接口 泛型方法 类型擦除 擦除的问题 边界 通配符 上界通配符 下界通配符 通配符和向上转型 泛型约束 实践总结 泛型 ...

  2. Java基础学习笔记二十三 Java核心语法之反射

    类加载器 类的加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,链接,初始化三步来实现对这个类进行初始化. 加载就是指将class文件读入内存,并为之创建一个Class对象.任 ...

  3. Java高级特性之反射学习总结

    老规矩我们还是先提出几个问题,一门技术必然要能解决一定的问题,才有去学习掌握它的价值 一. 什么是反射? 二.反射能做什么? 一. 什么是反射? 用在Java身上指的是我们可以于运行时加载.探知.使用 ...

  4. Java高级特性之反射

    老规矩我们还是先提出几个问题,一门技术必然要能解决一定的问题,才有去学习掌握它的价值 一. 什么是反射? 二.反射能做什么? 一. 什么是反射? 用在Java身上指的是我们可以于运行时加载.探知.使用 ...

  5. JAVA高级语法

    高级语法 第三章:面向对象和高级语法 实例化: 不实例化,就是一个空指针 注意,声明和实例化是两个过程.声明的过程是不分配内存空间的,只有实例化才会真正分配空间 对变量的分类 实例变量只有实例化之后才 ...

  6. Java 高级基础——反射

    Java 高级基础--反射 反射的意义:Java 强类型语言,但是我们在运行时有了解.修改信息的需求,包括类信息.成员信息以及数组信息. 基本类型与引用类型 基本类型,(固定的 8 种) 整数:byt ...

  7. java高级——反射

    慕课网<反射——Java高级开发必须懂的>听课笔记 一.class类的使用 class ClassDemo { public static void main(String[] args) ...

  8. Java高级特性——反射机制(第二篇)

    在Java高级特性——反射机制(第一篇)中,写了很多反射的实例,可能对于Class的了解还是有点迷糊,那么我们试着从内存角度去分析一下. Java内存 从上图可以看出,Java将内存分为堆.栈.方法区 ...

  9. Java高级特性 第5节 序列化和、反射机制

    一.序列化 1.序列化概述 在实际开发中,经常需要将对象的信息保存到磁盘中便于检索,但通过前面输入输出流的方法逐一对对象的属性信息进行操作,很繁琐并容易出错,而序列化提供了轻松解决这个问题的快捷方法. ...

随机推荐

  1. 自己常用的CMake用法总结

    欢迎指正 CMake : A.download : https://cmake.org/download/ B.tutorial: https://cmake.org/cmake-tutorial/ ...

  2. 1326 - Race

    1326 - Race   PDF (English) Statistics Forum Time Limit: 1 second(s) Memory Limit: 32 MB Disky and S ...

  3. .NET Core 实现动态代理做AOP(面向切面编程)

    1.介绍 1.1 动态代理作用 用动态代理可以做AOP(面向切面编程),进行无入侵式实现自己的扩展业务,调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,比如:日志记录.性能统计.安全控制.事务 ...

  4. MyBatis 流式查询

    流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果.流式查询的好处是能够降低内存使用. 流式查询的过程当中,数据库连接是保持打开状态的,因此要注意的是:执行一个 ...

  5. Vue.js高效前端开发 • 【Vue组件】

    全部章节 >>>> 文章目录 一.Vue组件介绍 1.组件概述 2.组件使用步骤 3.实践练习 一.Vue组件使用 1.组件注册 2.组件注册语法糖 3.使用script或te ...

  6. PHP-FPM 开启慢日志记录

    首先,找到 php-fpm 所在的目录: 使用 which php-fpm 使用 whereis php-fpm 然后找到 php-fpm.conf 所在的目录: 使用 ps -ef | grep p ...

  7. BOM 点击触发 倒计时发送验证码案例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. 大厂必问的Java集合面试题

    本文目录: 常见的集合有哪些? List .Set和Map 的区别 ArrayList 了解吗? ArrayList 的扩容机制? 怎么在遍历 ArrayList 时移除一个元素? Arraylist ...

  9. Linux根目录缺少x权限,产生的两个错误

    错误一:root用户执行systemctl命令报误 [root@node1 ~]# systemctl restart sshd * (pkttyagent:10364): WARNING *: Un ...

  10. Flask_Flask-Migrate数据迁移扩展(十二)

    在开发过程中,需要修改数据库模型,而且还要在修改之后更新数据库.最直接的方式就是删除旧表,但这样会丢失数据.更好的解决办法是使用数据库迁移框架,它可以追踪数据库模式的变化,然后把变动应用到数据库中. ...