S01-23 JavaSE-反射
[TOC]
反射的引入
需求
根据配置文件re.properties指定的类路径和方法名,创建对象并调用方法,无需修改源码(符合开闭原则)。
配置文件re.properties:
properties
classfullpath=com.hspedu.Cat
method=hi传统方式局限
无法通过字符串动态创建对象和调用方法,必须修改源码。
反射解决方案
java
package com.hspedu.reflection.question;
import java.io.FileInputStream;
import java.util.Properties;
import java.lang.reflect.Method;
import java.lang.reflect.Class;
/**
* 反射引入案例
*/
@SuppressWarnings({"all"})
public class ReflectionQuestion {
public static void main(String[] args) throws Exception {
// 1. 读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();
// 2. 反射机制创建对象并调用方法
Class cls = Class.forName(classfullpath); // 加载类
Object o = cls.newInstance(); // 创建对象
Method method = cls.getMethod(methodName); // 获取方法对象
method.invoke(o); // 调用方法
}
}反射机制
核心概念
- 反射机制允许程序在运行时获取类的内部信息(成员变量、构造器、方法等),并操作对象的属性和方法
- 类加载后,堆中会生成一个
Class类型的对象(一个类只有一个Class对象),包含类的完整结构信息,通过该对象可访问类的结构(类似“镜子”)
反射原理示意图
代码阶段(.java文件)→ 编译 → 字节码文件(.class)→ 类加载器(ClassLoader)→ 运行阶段(堆中的Class对象 + 实例对象)- Class对象:包含类的成员变量(Field)、构造器(Constructor)、方法(Method)等结构信息
- 通过Class对象可实现:创建实例、调用方法、操作属性
反射的核心功能
- 运行时判断任意对象所属的类
- 运行时构造任意类的对象
- 运行时获取任意类的成员变量和方法
- 运行时调用任意对象的成员变量和方法
- 生成动态代理
反射相关主要类
| 类名 | 功能说明 |
|---|---|
| java.lang.Class | 代表一个类,包含类的完整结构信息 |
| java.lang.reflect.Method | 代表类的方法,可调用方法 |
| java.lang.reflect.Field | 代表类的成员变量,可访问/修改属性 |
| java.lang.reflect.Constructor | 代表类的构造器,可创建对象 |
反射的优缺点
- 优点:动态创建和使用对象,灵活性高,是框架底层核心
- 缺点:解释执行,执行速度比传统方式慢
反射调用优化
- 使用
setAccessible(true)关闭访问检查,提高反射效率 - 适用场景:反射调用次数较多时
优化案例
java
package com.hspedu.reflection;
import com.hspedu.Cat;
import java.lang.reflect.Method;
/**
* 反射性能测试
*/
@SuppressWarnings({"all"})
public class Reflection02 {
public static void main(String[] args) throws Exception {
m1(); // 传统方式
m2(); // 反射方式
m3(); // 反射优化方式
}
// 传统方式
public static void m1() {
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("m1() 耗时=" + (end - start));
}
// 反射方式
public static void m2() throws Exception {
Class cls = Class.forName("com.hspedu.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("m2() 耗时=" + (end - start));
}
// 反射优化(关闭访问检查Java反射机制
获取Class 类对象(GetClass_.java)
- 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法
forName()获取,可能抛出ClassNotFoundException。- 实例:
Class cls1 = Class.forName("java.lang.Cat"); - 应用场景:多用于配置文件,读取类全路径,加载类。
- 实例:
- 前提:若已知具体的类,通过类的
class属性获取,该方式最为安全可靠,程序性能最高。- 实例:
Class cls2 = Cat.class; - 应用场景:多用于参数传递,比如通过反射得到对应构造器对象。
- 实例:
- 前提:已知某个类的实例,调用该实例的
getClass()方法获取Class对象。- 实例:
Class clazz = 对象.getClass();(获取运行类型) - 应用场景:通过创建好的对象,获取Class对象。
- 实例:
- 其他方式(通过类加载器):
ClassLoader cl = 对象.getClass().getClassLoader();Class clazz4 = cl.loadClass("类的全类名");
- 基本数据类型获取Class对象:
- 格式:
Class cls = 基本数据类型.class - 示例:
Class<Integer> integerClass = int.class;
- 格式:
- 基本数据类型包装类获取Class对象:
- 格式:
Class cls = 包装类.TYPE - 示例:
Class<Integer> type1 = Integer.TYPE;
- 格式:
java
package com.hspedu.reflection.class_;
import com.hspedu.Car;
/**
* @author 韩顺平
* @version 1.0
* 演示得到Class 对象的各种方式(6)
*/
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
// 1. Class.forName(通过读取配置文件获取全类名)
String classAllPath = "com.hspedu.Car";
Class<?> cls1 = Class.forName(classAllPath);
System.out.println(cls1);
// 2. 类名.class(应用场景:用于参数传递)
Class cls2 = Car.class;
System.out.println(cls2);
// 3. 对象.getClass()(应用场景:有对象实例)
Car car = new Car();
Class cls3 = car.getClass();
System.out.println(cls3);
// 4. 通过类加载器获取Class对象
// (1) 先得到类加载器
ClassLoader classLoader = car.getClass().getClassLoader();
// (2) 通过类加载器得到Class 对象
Class cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);
// 验证cls1, cls2, cls3, cls4 是同一个对象
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());
// 5. 基本数据类型获取Class对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass); // 输出int
// 6. 包装类通过.TYPE获取Class对象
Class<Integer> type1 = Integer.TYPE;
Class<Character> type2 = Character.TYPE;
System.out.println(type1);
System.out.println(integerClass.hashCode());
System.out.println(type1.hashCode());
}
}哪些类型有Class 对象
有Class 对象的类型
- 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void
应用实例(AllTypeClass.java)
java
package com.hspedu.reflection.class_;
import java.io.Serializable;
/**
* @author 韩顺平
* @version 1.0
* 演示哪些类型有Class 对象
*/
public class AllTypeClass {
public static void main(String[] args) {
Class<String> cls1 = String.class; // 外部类
Class<Serializable> cls2 = Serializable.class; // 接口
Class<Integer[]> cls3 = Integer[].class; // 数组
Class<float[][]> cls4 = float[][].class; // 二维数组
Class<Deprecated> cls5 = Deprecated.class; // 注解
Class<Thread.State> cls6 = Thread.State.class; // 枚举
Class<Long> cls7 = long.class; // 基本数据类型
Class<Void> cls8 = void.class; // void
Class<Class> cls9 = Class.class; // Class本身
System.out.println(cls1);
System.out.println(cls2);
System.out.println(cls3);
System.out.println(cls4);
System.out.println(cls5);
System.out.println(cls6);
System.out.println(cls7);
System.out.println(cls8);
System.out.println(cls9);
}
}类加载
基本说明
- 反射机制是Java实现动态语言的关键,核心是通过反射实现类动态加载。
- 静态加载:编译时加载相关类,若不存在则报错,依赖性强。
- 动态加载:运行时加载需要的类,若不使用则不报错,降低依赖性。
类加载时机
- 当创建对象时(new)→ 静态加载
- 当子类被加载时,父类也加载 → 静态加载
- 调用类中的静态成员时 → 静态加载
- 通过反射(
Class.forName("com.test.Cat"))→ 动态加载
类加载过程
类加载分为三个阶段:加载(Loading)→ 连接(Linking)→ 初始化(Initialization)
- 编译:Java源码(.java)→ 字节码文件(.class)
- 加载后内存布局:方法区存储类的字节码二进制数据,堆区存储类的Class对象
各阶段任务
| 阶段 | 核心任务 |
|---|---|
| 加载(Loading) | 将类的class文件读入内存,生成代表该类的java.lang.Class对象(由类加载器完成) |
| 连接(Linking)- 验证 | 确保Class文件字节流符合虚拟机要求,无安全风险(文件格式验证、元数据验证等) |
| 连接(Linking)- 准备 | 为静态变量分配内存(方法区),并设置默认初始化值(如0、null、false) |
| 连接(Linking)- 解析 | 将类的二进制数据合并到JRE中 |
| 初始化(Initialization) | 执行<clinit>()方法,收集静态变量赋值动作和静态代码块并合并执行 |
关键细节
- 验证阶段可通过
-Xverify:none参数关闭大部分验证,缩短加载时间。 - 准备阶段:静态常量(static final)直接赋值目标值,而非默认值;实例变量不参与。
- 初始化阶段:
<clinit>()方法在多线程环境中会被同步加锁,确保类仅初始化一次。
案例演示(ClassLoad02.java)
java
package com.hspedu.reflection.classload_;
/**
* @author 韩顺平
* @version 1.0
* 说明类加载的链接阶段-准备
*/
public class ClassLoad02 {
public static void main(String[] args) {
}
}
class A {
// 实例属性:准备阶段不分配内存
public int n1 = 10;
// 静态变量:准备阶段分配内存,默认初始化0(非20)
public static int n2 = 20;
// 静态常量:准备阶段直接赋值30
public static final int n3 = 30;
}案例演示(ClassLoad03.java)
java
package com.hspedu.reflection.classload_;
/**
* @author 韩顺平
* @version 1.0
* 演示类加载-初始化阶段
*/
public class ClassLoad03 {
public static void main(String[] args) throws ClassNotFoundException {
// 加载B类,触发初始化
new B();
// 或调用静态属性触发初始化:System.out.println(B.num);
}
}
class B {
static {
System.out.println("B 静态代码块被执行");
num = 300;
}
static int num = 100;
public B() {
System.out.println("B() 构造器被执行");
}
}通过反射获取类的结构信息
第一组:java.lang.Class 类方法
| 方法名 | 功能描述 |
|---|---|
| getName() | 获取全类名 |
| getSimpleName() | 获取简单类名 |
| getFields() | 获取所有public修饰的属性(本类+父类) |
| getDeclaredFields() | 获取本类中所有属性 |
| getMethods() | 获取所有public修饰的方法(本类+父类) |
| getDeclaredMethods() | 获取本类中所有方法 |
| getConstructors() | 获取本类所有public修饰的构造器 |
| getDeclaredConstructors() | 获取本类中所有构造器 |
| getPackage() | 以Package形式返回包信息 |
| getSuperClass() | 以Class形式返回父类信息 |
| getInterfaces() | 以Class[]形式返回接口信息 |
| getAnnotations() | 以Annotation[]形式返回注解信息 |
第二组:java.lang.reflect.Field 类方法
- getModifiers():以int形式返回修饰符(public=1、private=2、protected=4、static=8、final=16)
- getName():返回属性名
- getType():以Class形式返回属性类型
第三组:java.lang.reflect.Method 类方法
- getModifiers():以int形式返回修饰符(同Field)
- getReturnType():以Class形式返回返回类型
- getName():返回方法名
- getParameterTypes():以Class[]返回参数类型数组
第四组:java.lang.reflect.Constructor 类方法
- getModifiers():以int形式返回修饰符(同Field)
- getName():返回构造器全类名
- getParameterTypes():以Class[]返回参数类型数组
应用实例(ReflectionUtils.java)
java
package com.hspedu.reflection;
import org.junit.jupiter.api.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author 韩顺平
* @version 1.0
* 演示如何通过反射获取类的结构信息
*/
public class ReflectionUtils {
@Test
public void api_01() throws ClassNotFoundException {
// 得到Class 对象
Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
// 获取全类名和简单类名
System.out.println(personCls.getName()); // com.hspedu.reflection.Person
System.out.println(personCls.getSimpleName()); // Person
// 获取public修饰的属性(本类+父类)
Field[] fields = personCls.getFields();
for (Field field : fields) {
System.out.println("本类以及父类的public属性=" + field.getName());
}
// 获取本类中所有属性
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性=" + declaredField.getName());
}
// 获取public修饰的方法(本类+父类)
Method[] methods = personCls.getMethods();
for (Method method : methods) {
System.out.println("本类以及父类的public方法=" + method.getName());
}
// 获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法=" + declaredMethod.getName());
}
// 获取public修饰的构造器
Constructor<?>[] constructors = personCls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("本类的public构造器=" + constructor.getName());
}
// 获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("本类中所有构造器=" + declaredConstructor.getName());
}
// 获取包信息、父类信息、接口信息、注解信息
System.out.println("包信息=" + personCls.getPackage());
Class<?> superclass = personCls.getSuperclass();
System.out.println("父类的class对象=" + superclass);
Class<?>[] interfaces = personCls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("接口信息=" + anInterface);
}
Annotation[] annotations = personCls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息=" + annotation);
}
}
@Test
public void api_02() throws ClassNotFoundException {
Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
// 打印本类所有属性的详细信息
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("属性名=" + declaredField.getName()
+ " 修饰符值=" + declaredField.getModifiers()
+ " 类型=" + declaredField.getType());
}
// 打印本类所有方法的详细信息
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("方法名=" + declaredMethod.getName()
+ " 访问修饰符值=" + declaredMethod.getModifiers()
+ " 返回类型=" + declaredMethod.getReturnType());
// 打印方法形参类型
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("方法形参类型=" + parameterType);
}
}
// 打印本类所有构造器的详细信息
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("====================");
System.out.println("构造器名=" + declaredConstructor.getName());
// 打印构造器形参类型
Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("构造器形参类型=" + parameterType);
}
}
}
}
// 父类A
class A {
public String hobby;
public void hi() {}
public A() {}
public A(String name) {}
}
// 接口IA、IB
interface IA {}
@Deprecated
interface IB {}
// 测试类Person
class Person extends A implements IA, IB {
// 属性
public String name;
protected static int age; // 修饰符值:4+8=12
String job;
private double sal;
// 构造器
public Person() {}
public Person(String name) {}
private Person(String name, int age) {}
// 方法
public void m1(String name, int age, double sal) {}
protected String m2() { return null; }
void m3() {}
private void m4() {}
}通过反射创建对象
核心方法
| 类别 | 方法名 | 功能描述 |
|---|---|---|
| Class类 | newInstance() | 调用public无参构造器,获取对象 |
| Class类 | getConstructor(Class...clazz) | 根据参数列表,获取public构造器对象 |
| Class类 | getDeclaredConstructor(Class...clazz) | 根据参数列表,获取所有构造器对象 |
| Constructor类 | setAccessible(true) | 暴破:允许访问private构造器/方法/属性 |
| Constructor类 | newInstance(Object...obj) | 调用构造器,传入实参创建对象 |
案例演示(ReflecCreateInstance.java)
java
package com.hspedu.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author 韩顺平
* @version 1.0
* 演示通过反射机制创建实例
*/
public class ReflecCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
InstantiationException, NoSuchMethodException, InvocationTargetException {
// 1. 获取User类的Class对象
Class<?> userClass = Class.forName("com.hspedu.reflection.User");
// 2. 通过public无参构造器创建实例
Object o1 = userClass.newInstance();
System.out.println(o1);
// 3. 通过public有参构造器创建实例(User(String name))
Constructor<?> constructor = userClass.getConstructor(String.class);
Object o2 = constructor.newInstance("hsp");
System.out.println("o2=" + o2);
// 4. 通过private有参构造器创建实例(User(int age, String name))
Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
// 暴破:允许访问private构造器
constructor1.setAccessible(true);
Object o3 = constructor1.newInstance(100, "张三丰");
System.out.println("o3=" + o3);
}
}
// 测试类User
class User {
private int age = 10;
private String name = "韩顺平教育";
// public无参构造器
public User() {}
// public有参构造器
public User(String name) {
this.name = name;
}
// private有参构造器
private User(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}通过反射访问类中的成员
访问属性(ReflecAccessProperty.java)
核心步骤
- 根据属性名获取Field对象:
Field f = clazz.getDeclaredField(属性名); - 暴破:
f.setAccessible(true);(允许访问private属性) - 访问属性:
- 设置值:
f.set(o, 值);(o为对象,静态属性可传null) - 获取值:
f.get(o);
- 设置值:
案例演示
java
package com.hspedu.reflection;
import java.lang.reflect.Field;
/**
* @author 韩顺平
* @version 1.0
* 演示反射操作属性
*/
public class ReflecAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
InstantiationException, NoSuchFieldException {
// 1. 获取Student类的Class对象
Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");
// 2. 创建对象
Object o = stuClass.newInstance();
System.out.println(o.getClass()); // 运行类型:Student
// 3. 操作public属性age
Field ageField = stuClass.getField("age");
ageField.set(o, 88); // 设置age值为88
System.out.println(o); // 输出Student [age=88, name=null]
System.out.println(ageField.get(o)); // 输出88
// 4. 操作private static属性name
Field nameField = stuClass.getDeclaredField("name");
nameField.setAccessible(true); // 暴破
nameField.set(null, "老韩~"); // 静态属性,o可传null
System.out.println(o); // 输出Student [age=88, name=老韩~]
System.out.println(nameField.get(null)); // 输出老韩~
}
}
// 测试类Student
class Student {
public int age;
private static String name;
public Student() {}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}访问方法(ReflecAccessMethod.java)
核心步骤
- 根据方法名和参数列表获取Method对象:
Method m = clazz.getDeclaredMethod(方法名, 参数类型.class); - 暴破:
m.setAccessible(true);(允许访问private方法) - 调用方法:
Object returnValue = m.invoke(o, 实参列表);(静态方法o可传null)
案例演示
java
package com.hspedu.reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* @author 韩顺平
* @version 1.0
* 演示通过反射调用方法
*/
public class ReflecAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InstantiationException, InvocationTargetException {
// 1. 获取Boss类的Class对象
Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss");
// 2. 创建对象
Object o = bossCls.newInstance();
// 3. 调用public方法hi(String s)
Method hiMethod = bossCls.getDeclaredMethod("hi", String.class);
hiMethod.invoke(o, "韩顺平教育~"); // 输出:hi 韩顺平教育~
// 4. 调用private static方法say(int, String, char)
Method sayMethod = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
sayMethod.setAccessible(true); // 暴破
// 静态方法可传null调用
Object result1 = sayMethod.invoke(null, 100, "张三", '男');
System.out.println(result1); // 输出:100 张三 男
Object result2 = sayMethod.invoke(o, 200, "李四", '女');
System.out.println(result2); // 输出:200 李四 女
// 5. 调用返回对象的方法m1()
Method m1Method = bossCls.getDeclaredMethod("m1");
Object result3 = m1Method.invoke(o);
System.out.println("返回对象的运行类型=" + result3.getClass()); // 输出:class com.hspedu.reflection.Monster
}
}
class Monster {}
class Boss {
public int age;
private static String name;
// public方法
public void hi(String s) {
System.out.println("hi " + s);
}
// private static方法
private static String say(int n, String s, char c) {
return n + " " + s + " " + c;
}
// 返回Monster对象的方法
public Monster m1() {
return new Monster();
}
}本章作业
练习1:通过反射修改私有成员变量
java
// Homework01.java
class PrivateTest {
private String name = "hellokitty";
public String getName() {
return name;
}
}
// 要求:利用Class类得到私有name属性,修改其值,并调用getName()打印练习2:利用反射和File创建文件
java
// Homework02.java
// 要求:
// 1. 利用Class类的forName方法得到File类的class对象
// 2. 在控制台打印File类的所有构造器
// 3. 通过newInstance方法创建File对象,并创建E:\mynew.txt文件
// 提示:正常创建文件:File file = new File("d:\\aa.txt"); file.createNewFile();