反射的详解
目录
- 一、反射
- 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);}
}