当前位置: 首页 > news >正文

反射的详解

目录

    • 一、反射
      • 1.JDK,JRE,JVM的关系
      • 2.什么是反射
      • 3. 三种获取Class对象(类的字节码)的方式
      • 4.Class常用方法
      • 5. 获取类的构造器
      • 6.反射获取成员变量&使用
      • 7.反射获取成员方法
      • 8.综合例子

一、反射

1.JDK,JRE,JVM的关系

三者是Java运行环境的核心组成部分,从包含关系上看:JDK 包含JRE包含JVM,具体作用如下:

  • JDK(Java Development Kit,Java开发工具包)
    是Java开发的工具集合,包含 JRE + 编译器(javac)、调试工具(jdb)、文档工具(javadoc)等开发必需的工具。如果需要编写、编译Java程序,必须安装JDK。

  • JRE(Java Runtime Environment,Java运行时环境)
    运行Java程序的最小环境,包含 JVM + 运行Java程序必需的类库(如java.lang包等)。如果只需要运行Java程序(如 .class 或 .jar 文件),安装JRE即可。

  • JVM(Java Virtual Machine,Java虚拟机)
    是运行Java字节码的虚拟计算机,负责将字节码翻译成具体操作系统可执行的机器码。它是Java“一次编写,到处运行”(跨平台)的核心,不同操作系统需要安装对应的JVM实现。

  • 字节码的运行过程:

    编译阶段:开发者编写的Java源代码( .java 文件),通过JDK中的编译器(javac)编译成字节码文件( .class 文件),字节码是一种与平台无关的二进制指令。

    类加载:运行时,JVM的类加载器(ClassLoader)将 .class 文件(磁盘或者其他地方)加载到JVM内存中。

    字节码执行:JVM中的执行引擎(如解释器或即时编译器JIT)将字节码翻译成当前操作系统可识别的机器码,最终由操作系统执行机器码,完成程序功能。

    简单来说,字节码是Java跨平台的“中间语言”,通过JVM在不同系统上的适配,实现了“一次编译,到处运行”。

2.什么是反射

​ 反射是允许程序在运行期间可以获取类的类型( Class 对象(字节码对象)),成员变量,方法并可以动态的创建对象,访问属性,调用方法之类的操作。例如在往spring容器中注入一个bean对象,就是通过指定bean的class类型,然后spring框架在启动的时候就可以创建出这个bean对象。

具体:

1.“获取类的类型” 的本质:
这里的 “类型” 其实就是 Class 对象(字节码对象),它是反射的入口。比如 Spring 中指定 class="com.example.User",框架会通过 Class.forName("com.example.User") 获取 Class 对象,进而操作这个类。
2.动态性的核心体现:
你提到的 “动态创建对象、访问属性、调用方法”,核心是不需要在编译期写死具体类名或方法名。例如 Spring 配置文件中改一下 class 属性的值,无需重新编译代码,就能创建不同的对象,这就是反射动态性的价值。
3.框架中的典型流程:
以 Spring 注入 Bean 为例,反射的具体步骤是:
读取配置文件中的 class 属性(全类名);
通过 Class.forName() 获取 Class 对象;
通过 Constructor.newInstance() 动态创建实例(反射创建对象);
若有属性注入,再通过 Field.set() 动态设置属性值(反射操作属性)。

3. 三种获取Class对象(类的字节码)的方式

方法一:直接通过一个 class 的静态变量 class 获取:

Class cls = String.class;

方法二:如果我们有一个实例变量,可以通过该实例变量提供的 getClass () 方法获取:

String s = "Hello";
Class cls = s.getClass();

方法三:如果知道一个 class 的完整类名,可以通过静态方法 Class.forName () 获取:

Class cls = Class.forName("java.lang.String");

注意:因为 Class 实例在 JVM 中是唯一的,所以,上述方法获取的 class 实例是同一个实例。可以用 == 或hashcode()比较实例。

eg:

public class Test2 {public static void main(String[] args) throws ClassNotFoundException {Class  class1 = Class.forName("第二周.day3.Order");Class<Order> class2 = Order.class;Class class3 = new Order().getClass();System.out.println(class1.hashCode());//2003749087System.out.println(class2.hashCode());//2003749087System.out.println(class3.hashCode());//2003749087}
}
class  Order{static {System.out.println("静态代码块中Order已经执行!!!");}
}

4.Class常用方法

获取父类的Class,获取实现接口的Class:

public class Test3 {public static void main(String[] args) {Class<String> stringClass = String.class;System.out.println("完全限定类名:" + stringClass.getName());System.out.println("完全限定类名:" + stringClass.getTypeName());System.out.println("仅包含类名:" + stringClass.getSimpleName());System.out.println("所在的包名:" + stringClass.getPackage());// 获取父类的 Class 对象//String 类继承自 java.lang.Object,所以返回 Object 类的 Class 实例Class<? super String> superclass = stringClass.getSuperclass();System.out.println("父类:" + superclass);//  获取类实现的接口(String 类实现了多个接口,如 Serializable、Comparable等)Class[] interfaces = stringClass.getInterfaces();for (Class anInterface : interfaces) {System.out.println("接口:" + anInterface);}}
}

判断继承关系:

public class Test7 {public static void main(String[] args) {// instanceof 关键字:判断对象是否是某个类型的实例(被注释示例)// Integer n = 2;// System.out.println(n instanceof Integer); // true(自身类型)// System.out.println(n instanceof Number);  // true(父类)// isAssignableFrom:判断类型B能否赋值给类型A(Class类方法)// 1. Integer能否赋值给Integer(自身类型)System.out.println(Integer.class.isAssignableFrom(Integer.class)); // true// 2. Number能否赋值给Integer(父类→子类,不成立)System.out.println(Integer.class.isAssignableFrom(Number.class));  // false// 3. Integer能否赋值给Number(子类→父类,成立)System.out.println(Number.class.isAssignableFrom(Integer.class));  // true}
}

判断Class的类型:

public class Test3 {public static void main(String[] args) {// 获取不同类型的Class对象Class clz1 = String.class;        // 普通类Class clz2 = DayOfWeek.class;     // 枚举类Class clz3 = String[].class;      // 数组类型Class clz4 = List.class;          // 接口// 调用info方法打印类信息info(clz1);info(clz2);info(clz3);info(clz4);}// 打印类的基本类型信息public static void info(Class clazz) {System.out.println("类信息:" + clazz);System.out.println("是否是接口? " + clazz.isInterface());System.out.println("是否是枚举? " + clazz.isEnum());System.out.println("是否是数组? " + clazz.isArray());System.out.println("---------------------"); // 分隔线}
}

5. 获取类的构造器

过字节码对象获取构造器,并使用构造器创建对象。

在这里插入图片描述

get:获取
Declared: 有这个单词表示可以获取任意一个,没有这个单词表示只能获取一个public修饰的
Constructor: 构造方法的意思
后缀s: 表示可以获取多个,没有后缀s只能获取一个

反射获取构造器的作用:初始化对象并返回

在这里插入图片描述

eg:

package 第二周.day3;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;public class Test5 {public static void main(String[] args)throws NoSuchMethodException,  // 当找不到指定构造方法时抛出InvocationTargetException,  // 构造方法执行过程中抛出异常时包装此异常InstantiationException,  // 当类是抽象类或接口等无法实例化时抛出IllegalAccessException  // 构造方法访问权限不足时抛出{// 获取Animal类的Class对象// Class对象是反射的核心,包含了类的所有元信息(构造方法、方法、字段等)Class clazz = Animal.class;// 获取指定参数列表的构造方法// getConstructor()方法参数为构造方法的参数类型对应的Class对象Constructor constructor0 = clazz.getConstructor(); // 获取无参构造方法Constructor constructor1 = clazz.getConstructor(int.class); // 获取接收int类型参数的构造方法Constructor constructor2 = clazz.getConstructor(int.class, double.class); // 获取接收int和double类型参数的构造方法// 通过反射获取的Constructor对象创建Animal实例// newInstance()方法的参数为实际传递给构造方法的参数值System.out.println("===========");Object o1 = constructor0.newInstance();  // 调用无参构造方法创建实例// 打印创建的对象(默认调用Object类的toString()方法,输出类名@哈希码)System.out.println(o1);System.out.println("===========");Object o2 = constructor1.newInstance(23);  // 调用int参数的构造方法,传入23System.out.println(o2);System.out.println("===========");Object o3 = constructor2.newInstance(23, 3.14);  // 调用int和double参数的构造方法,传入23和3.14System.out.println(o3);}
}class Animal {// 静态代码块:在类加载阶段执行,且只执行一次// 用于验证类加载的时机(当获取Class对象时会触发类加载)static {System.out.println("Animal类被加载!!!!");}// 无参构造方法public Animal() {System.out.println("无参构造方法被执行。");}// 接收int类型参数的构造方法public Animal(int a) {System.out.println("有参构造方法被执行:a = " + a);}// 接收int和double类型参数的构造方法public Animal(int a, double b) {System.out.println("有参构造方法被执行:a = " + a + ",b = " + b);}
}

6.反射获取成员变量&使用

在Class类中提供了获取成员变量的方法:

在这里插入图片描述

再次强调一下设置值、获取值的方法时Filed类的需要用Filed类的对象来调用,而且不管是设置值、还是获取值,都需要依赖于该变量所属的对象。代码如下:

设置值:

import java.lang.reflect.Field;// 演示 Java 反射机制:通过 Class 对象操作 Employee 类的私有成员
public class Test8 {public static void main(String[] args) throws Exception {// ========== 1. 获取 Class 对象(反射入口) ==========// 方式:类名.class,获取 Employee 类的字节码元数据Class clazz = Employee.class;// ========== 2. 反射创建对象 ==========// 调用无参构造器实例化对象(要求 Employee 有无参构造)Object obj = clazz.newInstance();// ========== 3. 反射获取私有字段 ==========// getDeclaredField 可获取私有字段,需配合 setAccessible(true) 突破封装Field enoField = clazz.getDeclaredField("eno");Field nameField = clazz.getDeclaredField("realName");Field salaryField = clazz.getDeclaredField("salary");// ========== 4. 暴力反射:开启私有字段访问权限 ==========// 即使字段是 private,也能通过此方法赋值enoField.setAccessible(true);nameField.setAccessible(true);salaryField.setAccessible(true);// ========== 5. 反射赋值私有字段 ==========// 等价于:obj.eno = "T001"; enoField.set(obj, "T001");// 等价于:obj.realName = "水产张总";nameField.set(obj, "水产张总");// 等价于:obj.salary = 456.778;salaryField.setDouble(obj, 456.778);// ========== 6. 验证结果 ==========// 调用 Employee 的 toString() 输出对象内容System.out.println(obj);}
}// 普通 Java 类,包含私有字段和 toString 方法
class Employee {// 私有字段:体现封装性private String eno;private String realName;private String phoneNumber;private int level;private double salary;// 重写 toString,自定义对象打印格式@Overridepublic String toString() {return "Employee{" +"eno='" + eno + '\'' +", realName='" + realName + '\'' +", phoneNumber='" + phoneNumber + '\'' +", level=" + level +", salary=" + salary +'}';}
}

取值:

public class Test9 {public static void main(String[] args) throws IllegalAccessException {// 1. 创建 Employee 对象(通过构造器传参初始化)Employee emp = new Employee("T002", "孙乐", "1310988442", 8, 4567.8);// 2. 调用 info 方法,反射打印对象字段信息info(emp);}public static void info(Object obj) throws IllegalAccessException {// 3. 获取对象的 Class 对象(反射入口)Class clazz = obj.getClass();// 4. 获取类的所有**声明的字段**(包括 private)Field[] fields = clazz.getDeclaredFields();// 5. 遍历字段,逐个处理for (Field f : fields) {// 6. 暴力反射:突破 private 限制(允许访问私有字段)f.setAccessible(true);// 7. 获取字段的类型(判断是 int、double 还是其他类型)Class fieldType = f.getType();// 8. 根据字段类型,调用不同的 get 方法if (fieldType == int.class) {// int 类型:用 getInt 获取值System.out.printf("%s = %s%n", f.getName(), f.getInt(obj));} else if (fieldType == double.class) {// double 类型:用 getDouble 获取值System.out.printf("%s = %s%n", f.getName(), f.getDouble(obj));} else {// 其他类型(如 String):用 get 获取值System.out.printf("%s = %s%n", f.getName(), f.get(obj));}}}
}// 员工类:包含私有字段和构造器
class Employee {// 私有字段(体现封装性)private String eno;private String realName;private String phoneNumber;private int level;private double salary;// 无参构造(未使用,但保留)public Employee() {}// 全参构造:用于初始化对象public Employee(String eno, String realName, String phoneNumber, int level, double salary) {this.eno = eno;this.realName = realName;this.phoneNumber = phoneNumber;this.level = level;this.salary = salary;}// 重写 toString:自定义对象打印格式(main 中未直接调用,但可用于调试)@Overridepublic String toString() {return "Employee{" +"eno='" + eno + '\'' +", realName='" + realName + '\'' +", phoneNumber='" + phoneNumber + '\'' +", level=" + level +", salary=" + salary +'}';}
}

7.反射获取成员方法

在Java中反射包中,每一个成员方法用Method对象来表示,通过Class类提供的方法可以获取类中的成员方法对象。

在这里插入图片描述

eg:

import java.lang.reflect.Method;// Class 类型:用来封装某一个class类的类型信息(类名、构造方法、成员变量[字段]、实例方法)
// Constructor类型:用来封装一个构造方法
// Field类型:用来封装一个成员变量[字段]
// Method类型:用于封装一个方法
public class Test10 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 硬编码// String s = new String("just do IT");// String result = s.substring(0,4);// System.out.println(result);//1、反射第一步:先获取到Class对象Class clazz = String.class;// 创建实例对象Constructor constructor = clazz.getConstructor(String.class);Object s = constructor.newInstance("just do IT");// 根据方法名称,获取对应的Method对象Method method = clazz.getDeclaredMethod("substring", int.class, int.class);// invoke()执行方法// 硬编码 String result = s.substring(0,4);Object returnValue = method.invoke(s, 0, 4);System.out.println("返回值:" + returnValue);// 获取String类的所有定义方法// Method[] methods = clazz.getDeclaredMethods();// for (Method method : methods){//     System.out.println(method);// }}
}

eg:静态方法的调用,传对象为null

import java.lang.reflect.Method;// 静态方法的调用
public class Test11 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {// ========== 硬编码方式(被注释示例) ==========// 直接调用 Math.log10(567),并输出结果+1// int result = (int) Math.log10(567);// System.out.println(result + 1);// ========== 反射方式调用静态方法 ==========// 1. 获取 Math 类的 Class 对象Class clazz = Math.class;// 2. 获取静态方法 log10:参数类型是 doubleMethod method = clazz.getDeclaredMethod("log10", double.class);// 3. 调用静态方法:因静态方法属于类,而非对象,所以 invoke 的第一个参数传 nullObject returnValue = method.invoke(null, 567.0);// 4. 输出反射调用结果System.out.println(returnValue);}
}

8.综合例子

package 第二周.day3;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;public class Test13 {public static void main(String[] args) {Scanner input = new Scanner(System.in);try {while (true) {System.out.print("请输入目标工具类名:");String className = input.nextLine(); // 第二周.day3.CommandSystem.out.print("请输入目标方法名称:");String methodName = input.nextLine(); // growif ("exit".equals(className) && "null".equals(methodName)) {System.out.println("程序退出了");break;}// 判断方法是否有参数,并执行该方法Class classzz = Class.forName(className);Method[] declaredMethods = classzz.getDeclaredMethods();Method targetmethod = null;for (Method method : declaredMethods) {if (methodName.equals(method.getName())) {targetmethod = method;break;}}if (targetmethod == null) {System.out.println("目标方法" + methodName + "不存在");continue;}Object[] objs = new Object[targetmethod.getParameterCount()];//遍历的当前方法有几个参数,就设置几次容量,传几次参数for (int i = 1; i <= targetmethod.getParameterCount(); i++) {System.out.println("请输入第:" + i + "个参数");objs[i - 1] = input.nextInt();}//创建实例Constructor constructor = classzz.getConstructor();Object o = constructor.newInstance();targetmethod.invoke(o, objs);}} catch (ClassNotFoundException | NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} finally {input.close();}}
}class Command {public Command() {}public void clear() {System.out.println("执行清空操作!");}public void init(int initSize, int initLocation) {System.out.println("执行初始化操作!");System.out.println("初始化长度 = " + initSize);System.out.println("初始化位置 = " + initLocation);}public void grow(int size) {System.out.println("执行扩容操作!");System.out.println("扩容量 = " + size);}
}
http://www.dtcms.com/a/328679.html

相关文章:

  • CAP理论深度解析与工程实践指南
  • USB基础 -- USB2.0设备插入的过程
  • 陕西西安一家IPO四年亏损近25亿负债率攀升,控制权稳定性遭质疑
  • 力扣121:买卖股票的最佳时机
  • 100、【OS】【Nuttx】【构建】cmake 配置保存
  • Xsens惯性动作捕捉系统
  • 数据库事务隔离:详解及Java面试题
  • MyBatis-Plus 分页失效问题解析:@Param 注解的影响与解决方案
  • amis表单较验
  • Datawhale AI夏令营第三期多模态RAG方向 Task3
  • AAAI论文速递 | NEST:超图小世界网络让自动驾驶轨迹预测更精准
  • 基于R语言的现代贝叶斯统计学方法(贝叶斯参数估计、贝叶斯回归、贝叶斯计算实践过程
  • 从聚合到透视:SQL 窗口函数的系统解读
  • 谷歌、facebook、tiktok广告账户多开怎么安全?亚马逊、ebay、shopee多店铺怎么做好?看看adspower工具,注册免费试用及实用技巧分享
  • SQL详细语法教程(一)--数据定义语言(DDL)
  • 基于R语言的现代贝叶斯统计学方法(贝叶斯参数估计、贝叶斯回归、贝叶斯计算)实践
  • 4G模块 ML307A通过MQTT协议连接到阿里云
  • 数据科学与爬虫技术学习笔记
  • 基于机器学习的自动驾驶汽车新型失效运行方法
  • Win11和Mac设置环境变量
  • 【汽车标定数据】动态优先级线程池在异步多文件解析中的应用
  • 2022 年全国硕士研究生招生考试真题笔记
  • 深度学习赋能汽车制造缺陷检测
  • “我店模式”:零售转型中的场景化突围
  • 美团搜索推荐统一Agent之交互协议与多Agent协同
  • 【计算机网络 | 第6篇】计算机体系结构与参考模型
  • go学习笔记-匿名函数
  • 算法题笔记
  • Java连接MySQL数据库
  • Socket 套接字常用方法