JAVA作业:了解Java的反射机制
·
一、什么是反射?
1.1 定义
反射(Reflection) 是Java语言提供的一种强大机制,允许程序在运行时动态地获取类的完整信息(如成员变量、方法、构造方法等),并且可以动态地创建对象、调用方法、访问属性。
通俗地说:反射就是在程序运行时,让Java程序能够“ introspection”(自省),看清自己的结构,甚至修改自己的行为。
1.2 正常方式 vs 反射方式
| 对比项 | 正常方式 | 反射方式 |
|---|---|---|
| 时机 | 编译时确定 | 运行时动态确定 |
| 类的信息 | 写代码时已知 | 运行时才获取 |
| 对象创建 | new 类名() |
Class.forName("类名").newInstance() |
| 方法调用 | 对象.方法名() |
method.invoke(对象, 参数) |
| 特点 | 高效、类型安全 | 灵活、但有一定性能开销 |
简单示例:
// 正常方式:编译时就知道要使用Student类
Student s = new Student();
s.study();
// 反射方式:运行时才知道要使用哪个类
Class<?> clazz = Class.forName("com.example.Student");
Object obj = clazz.newInstance();
Method method = clazz.getMethod("study");
method.invoke(obj);
二、为什么需要反射?
2.1 核心价值
反射机制使得Java程序从“静态”变为“动态”:
| 能力 | 说明 |
|---|---|
| 动态加载类 | 运行时才决定加载哪个类 |
| 动态创建对象 | 类名作为字符串传入,无需硬编码 |
| 动态调用方法 | 方法名作为字符串传入 |
| 访问私有成员 | 可以绕过访问权限检查 |
2.2 典型应用场景
| 场景 | 说明 |
|---|---|
| IDE自动提示 | 编辑器通过反射获取类的方法和属性 |
| Spring框架 | IoC容器通过反射创建和管理Bean |
| JUnit框架 | 通过反射识别@Test注解的方法并执行 |
| Tomcat等Web容器 | 动态加载Servlet类 |
| JSON解析库 | 通过反射将JSON数据映射到Java对象 |
| 注解处理器 | 运行时读取注解信息 |
三、反射的核心API
3.1 Class类
Class类是反射的入口,每个Java类在运行时都会对应一个Class对象。
获取Class对象的三种方式:
// 方式1:通过类名.class
Class<?> clazz1 = String.class;
// 方式2:通过对象.getClass()
String str = "hello";
Class<?> clazz2 = str.getClass();
// 方式3:通过Class.forName()(最常用,动态加载)
Class<?> clazz3 = Class.forName("java.lang.String");
3.2 反射核心类
| 类名 | 作用 |
|---|---|
Class |
代表一个类或接口 |
Field |
代表类的成员变量 |
Method |
代表类的方法 |
Constructor |
代表类的构造方法 |
Modifier |
访问修饰符的工具类 |
3.3 常用方法一览
Class类常用方法:
| 方法 | 说明 |
|---|---|
getName() |
获取类全名(包名+类名) |
getSimpleName() |
获取类简单名称 |
getFields() |
获取所有public成员变量 |
getDeclaredFields() |
获取所有声明的成员变量(包括private) |
getMethods() |
获取所有public方法(包括继承的) |
getDeclaredMethods() |
获取所有声明的方法(不包括继承的) |
getConstructors() |
获取所有public构造方法 |
getDeclaredConstructors() |
获取所有声明的构造方法 |
newInstance() |
创建实例(调用无参构造) |
四、反射的基本使用
4.1 准备工作:定义一个示例类
/**
* 示例类:用于演示反射机制
*/
public class Person {
// 成员变量
private String name;
public int age;
// 构造方法
public Person() {
System.out.println("无参构造方法被调用");
}
private Person(String name) {
this.name = name;
System.out.println("私有构造方法被调用,name=" + name);
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("有参构造方法被调用:" + name + "," + age);
}
// 成员方法
public void sayHello() {
System.out.println("Hello, I'm " + name);
}
private void secret() {
System.out.println("这是一个私有方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
4.2 获取类的信息
import java.lang.reflect.*;
public class ReflectionInfoDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 1. 获取Class对象
Class<?> clazz = Class.forName("Person");
// 2. 获取类名
System.out.println("类全名:" + clazz.getName());
System.out.println("简单类名:" + clazz.getSimpleName());
// 3. 获取所有public方法(包括继承的)
System.out.println("\n=== 所有public方法 ===");
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.println(" " + m.getName());
}
// 4. 获取所有声明的成员变量
System.out.println("\n=== 所有声明的成员变量 ===");
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
System.out.println(" " + Modifier.toString(f.getModifiers()) + " " + f.getName());
}
// 5. 获取所有构造方法
System.out.println("\n=== 所有构造方法 ===");
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> c : constructors) {
System.out.println(" " + c);
}
}
}
4.3 通过反射创建对象
import java.lang.reflect.*;
public class ReflectionCreateDemo {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("Person");
// 方式1:调用无参构造方法
System.out.println("=== 方式1:无参构造 ===");
Person p1 = (Person) clazz.newInstance(); // JDK9后可能过时
p1.setName("张三");
p1.sayHello();
// 方式2:调用有参构造方法(推荐)
System.out.println("\n=== 方式2:有参构造 ===");
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Person p2 = (Person) constructor.newInstance("李四", 20);
p2.sayHello();
// 方式3:调用私有构造方法
System.out.println("\n=== 方式3:私有构造 ===");
Constructor<?> privateCon = clazz.getDeclaredConstructor(String.class);
privateCon.setAccessible(true); // 突破私有访问限制
Person p3 = (Person) privateCon.newInstance("王五");
p3.sayHello();
}
}
4.4 通过反射调用方法
import java.lang.reflect.*;
public class ReflectionMethodDemo {
public static void main(String[] args) throws Exception {
// 创建对象
Class<?> clazz = Class.forName("Person");
Constructor<?> con = clazz.getConstructor(String.class, int.class);
Person person = (Person) con.newInstance("小明", 18);
// 调用public方法
System.out.println("=== 调用public方法 ===");
Method sayHello = clazz.getMethod("sayHello");
sayHello.invoke(person);
// 调用带参数的方法
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(person, "小红");
sayHello.invoke(person);
// 调用私有方法(需要setAccessible)
System.out.println("\n=== 调用私有方法 ===");
Method secret = clazz.getDeclaredMethod("secret");
secret.setAccessible(true); // 突破私有权限
secret.invoke(person);
// 调用有返回值的方法
Method getName = clazz.getMethod("getName");
String name = (String) getName.invoke(person);
System.out.println("getName返回值:" + name);
}
}
4.5 通过反射访问成员变量
以下是整理后的代码块格式:
import java.lang.reflect.*;
public class ReflectionFieldDemo {
public static void main(String[] args) throws Exception {
// 创建对象
Class<?> clazz = Class.forName("Person");
Constructor<?> con = clazz.getConstructor(String.class, int.class);
Person person = (Person) con.newInstance("张三", 25);
System.out.println("原始对象:" + person);
// 访问public成员变量
System.out.println("\n=== 访问public成员变量 ===");
Field ageField = clazz.getField("age");
System.out.println("当前age:" + ageField.get(person));
ageField.set(person, 30);
System.out.println("修改后age:" + ageField.get(person));
// 访问私有成员变量(需要setAccessible)
System.out.println("\n=== 访问私有成员变量 ===");
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
System.out.println("当前name:" + nameField.get(person));
nameField.set(person, "李四");
System.out.println("修改后name:" + nameField.get(person));
System.out.println("最终对象:" + person);
}
}
五、反射的实际应用示例
示例1:通用的toString方法
import java.lang.reflect.*;
class ObjectUtil {
public static String toString(Object obj) {
if (obj == null) return "null";
Class<?> clazz = obj.getClass();
StringBuilder sb = new StringBuilder();
sb.append(clazz.getSimpleName()).append(" { ");
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
try {
sb.append(fields[i].getName()).append("=");
sb.append(fields[i].get(obj));
if (i < fields.length - 1) {
sb.append(", ");
}
} catch (IllegalAccessException e) {
sb.append("?");
}
}
sb.append(" }");
return sb.toString();
}
}
class Student {
private String name;
private int score;
Student(String name, int score) {
this.name = name;
this.score = score;
}
}
public class GenericToStringDemo {
public static void main(String[] args) {
Student s = new Student("张三", 95);
System.out.println(ObjectUtil.toString(s));
}
}
示例2:动态调用任意方法
import java.lang.reflect.*;
class DynamicInvoker {
public static Object invoke(Object obj, String methodName, Object... args) {
try {
Class<?> clazz = obj.getClass();
Class<?>[] paramTypes = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
paramTypes[i] = args[i].getClass();
}
Method method = clazz.getMethod(methodName, paramTypes);
return method.invoke(obj, args);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
class Calculator {
public int add(int a, int b) {
return a + b;
}
public String greet(String name) {
return "Hello, " + name;
}
}
public class DynamicInvokeDemo {
public static void main(String[] args) {
Calculator calc = new Calculator();
Object result1 = DynamicInvoker.invoke(calc, "add", 10, 20);
System.out.println("add结果:" + result1);
Object result2 = DynamicInvoker.invoke(calc, "greet", "张三");
System.out.println("greet结果:" + result2);
}
}
示例3:模拟JUnit的@Test注解
import java.lang.annotation.*;
import java.lang.reflect.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Test {}
class MyTestClass {
@Test
public void testMethod1() {
System.out.println("执行测试方法1");
}
@Test
public void testMethod2() {
System.out.println("执行测试方法2");
}
public void normalMethod() {
System.out.println("普通方法不会被自动执行");
}
}
class TestRunner {
public static void run(Class<?> testClass) throws Exception {
Object instance = testClass.newInstance();
Method[] methods = testClass.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Test.class)) {
System.out.print("正在执行:");
method.invoke(instance);
}
}
}
}
public class AnnotationDemo {
public static void main(String[] args) throws Exception {
TestRunner.run(MyTestClass.class);
}
}
运行结果
正在执行:执行测试方法1
正在执行:执行测试方法2
六、反射的优缺点
6.1 优点
| 优点 | 说明 |
|---|---|
| 动态性 | 运行时动态加载类,提高程序灵活性 |
| 通用性 | 可以编写通用的代码处理各种类型的对象 |
| 框架基础 | 几乎所有Java框架(Spring、Hibernate等)都依赖反射 |
| 绕过限制 | 可以访问私有成员,用于特殊场景(如调试工具) |
6.2 缺点
| 缺点 | 说明 |
|---|---|
| 性能开销 | 反射调用比直接调用慢,因为需要动态解析 |
| 安全问题 | 可以绕过访问权限,破坏封装性 |
| 代码可读性 | 反射代码不如直接调用直观 |
| 编译时检查缺失 | 错误只能在运行时暴露 |
七、性能对比示例
以下是整理后的代码块格式:
/**
* 对比直接调用与反射调用的性能
*/
public class PerformanceCompare {
public static void main(String[] args) throws Exception {
Person person = new Person("张三", 20);
// 1. 直接调用
long start1 = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
person.sayHello();
}
long end1 = System.nanoTime();
System.out.println("直接调用耗时:" + (end1 - start1) / 1000000 + " ms");
// 2. 反射调用
Method method = Person.class.getMethod("sayHello");
long start2 = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
method.invoke(person);
}
long end2 = System.nanoTime();
System.out.println("反射调用耗时:" + (end2 - start2) / 1000000 + " ms");
// 3. 反射调用(启用访问检查关闭)
method.setAccessible(true);
long start3 = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
method.invoke(person);
}
long end3 = System.nanoTime();
System.out.println("反射(setAccessible)耗时:" + (end3 - start3) / 1000000 + " ms");
}
}
代码结构说明:
- 完整保留了原始注释和逻辑结构
- 使用标准的Java代码块格式
- 保持了原有的三个测试部分(直接调用、反射调用、反射调用优化)
- 时间统计单位统一转换为毫秒(ms)
结论:反射调用通常比直接调用慢2-3倍,setAccessible(true)可以略微提升性能。
八、总结
8.1 核心知识点回顾
| 知识点 | 要点 |
|---|---|
| 反射的定义 | 运行时动态获取类信息并操作对象 |
| 获取Class对象 | .class、getClass()、Class.forName() |
| 核心API | Class、Field、Method、Constructor |
| 访问私有成员 | setAccessible(true) |
| 主要应用 | Spring框架、JUnit、JSON解析等 |
8.2 使用建议
-
优先使用直接调用:除非确实需要动态性,否则不要滥用反射
-
缓存反射对象:如果需要频繁调用,缓存Method、Field等对象
-
注意安全性:反射可以破坏封装,谨慎使用
-
考虑性能影响:对性能敏感的代码避免使用反射
8.3 作业完成情况
-
理解反射机制的定义和作用
-
掌握获取Class对象的三种方式
-
能够通过反射创建对象、调用方法、访问属性
-
了解反射的实际应用场景
-
了解反射的优缺点
九、课后练习
-
编写代码,通过反射获取
ArrayList类的所有方法名称 -
使用反射调用
String类的toUpperCase()方法 -
通过反射创建一个
HashMap对象,并调用put和get方法 -
尝试修改一个
String对象的内容(利用反射突破不可变性)
更多推荐


所有评论(0)