引言

在Java开发中,我们经常会听到”反射“这个词。很多框架如(Spring、MyBatis)的核心都离不开反射,但它又被认为性能差、破坏封装性。那么发射到底是什么?它有什么用?我们又该如何正确使用它?
本文将带你从基础概念出发,逐步掌握Java反射机制的核心API、经典应用场景以及避坑指南,让你在面试和工作中都能游刃有余。


一、什么是反射?

反射(Reflection)是Java语言提供的一种能力,它允许程序在运行时动态地获取类的完整信息,并且可以操作类或对象的属性、方法、构造器等,即使这些信息在编译时时未知的。

简单来说:

平时我们写代码是”先有类,后有对象,再调用方法“;而反射可以让你”先拿到一个对象,再反过来获取它属于哪个类、有哪些方法,然后动态调用“。

Java反射机制主要围绕java.lang.reflect包展开,核心类包括:

  • Class:代表一个类或接口
  • Field:代表类的成员变量
  • Method:代表类的方法
  • Constructor:代表类的构造方法

二、获取Class对象的三种方式

一切反射操作的起点都是Class对象。获取它的方式有三种:

1.通过类名.class

Class<String> clazz=String.class;

适用于编译时已明确知道类名的情况。

2.通过对象.getClass()

String str="hello";
Class<?> clazz=str.getClass();

适用于已有对象实例。

3.通过Class.forName(“全限定类名”)

Class<?> clazz=Class.forName("java.util.ArrayList");

最常用,因为类名可以来自配置文件或者数据库,真真俄国实现动态加载。


三、核心API详解

假如我们有一个简单的Person类:

public class Person{
	private String name;
	public int age;
	public Person(){}
	public Person(String name,int age){
		this.name=name;
		this.age=age;
	}
	private void sayHello(){
	System.out.println("Hello,I'm"+name);
	}
	public void setName(String name){
		this.name=name;
	}
}

1.操作构造方法-创建对象

Class<?> clazz=Class.forName("com.example.Person");
//
Constructor<?> constructor=clazz.getConstructor(String.class,int .class);
Person p2=(Person)constructor.newInstance("张三",25);

2.操作成员变量-读取/修改属性

Person P=new Person("李四"30);
Class<?> clazz=p.getClass();
//获取public属性(直接可访问)
Field ageField=clazz.getField("age");
System.out.println(ageField.get(p));//30
ageField.set(p,31);
//获取private属性(需要暴力破解)
Field nameField=clazz.getDeclaredField("name");
nameField.setAccessible(true);//绕过访问权限检查
System.out.println(nameField.get(p));//李四
nameField.set(p,"王五");

3.操作方法-动态调用

Person p=new Person("赵六",28);
Class<?> clazz=p.getClass();
//调用public方法
Method setNameMethod=clazz.getMethod("setName",String.class);
setNameMethod.invoke(p,"钱七");
//调用private方法
Method sayHelloMethod=clazz.getDeclareMethod("sayHello");
sayHelloMethod.setAccessible(true);
sayHelloMethod.setAccessible(true);
sayHelloMethod.invoke(p);

4.获取类的完整信息

反射还能获取类实现的接口、父类、注解等:

Class<?> clazz=ArrayList.class;
System.out.println(clazz.getName());
System.out.println(clazz.getSupeclass());
Class<?>[] interfaces=clazz.getInerfaces();

四、反射的应用场景

反射之所以重要,是因为它支撑起了大量框架和工具的实现。

1.框架的基石(loC容器)

Spring框架中,我们只需要在XML或注解里配置一个类名,框架就能自动创建对象并注入依赖,核心就是通过反射:

String className="com.example.service.UserService";
Class<?> clazz=Class.forName(className);
Object bean=clazz.getDeclaredConstructor().newInstance();
//再通过反射注入属性...

2.动态代理

Java动态代理(java.lang.reflect.Proxy)结合反射,可以再运行时生成代理类,实现AOP切面逻辑,如事务管理、日志记录等。

3.ORM框架(如MyBatis)

MyBatis将数据库查询结果自动映射为Java对象时,底层就是通过反射来获取实体类的字段,并调用setter方法赋值。

4.注解处理器

很多框架在运行时扫描类上的注解,再通过反射执行对应的逻辑。例如JUnit的@Test标记的方法,就是框架反射调用并执行。

5.IDE的智能提示

我们在IDE里敲对象名然后按下“.”,能列出所有方法,这也是IDE通过反射获取信息类实现的。


五、反射的优缺点

优点

  • 动态性:运行时加载、探知、操作编译时未知的类。
  • 灵活性:极大提升了框架和工具的扩展性。
  • 解耦:通过类名字符串即可完成对象创建和调用,适合配置化开发。

缺点

  • 性能较低:反射调用涉及安全检查、包装等额外操作,比直接调用慢很多。频繁使用时建议缓存Class、Method等对象。
  • 破坏封装性:setAccessible(true)可以绕过访问控制,降低安全性。
  • 代码可读性差:反射代码比直接调用复杂得多,排查问题也麻烦。

性能对比建议
对于普通业务方法调用,优先使用直接调用;只有在必须动态化(如框架底层)时才使用反射,并配合缓存减少开销。


六、避坑指南

1.异常处理地狱
反射操作会抛出大量受检异常(ClassNorFoundException,NoSuchMethodException,InvocationTargetException等),务必做好异常处理,避免程序崩溃。
2.setAccessible(true)谨慎使用
尤其在模块化环境(JDK9+)或安全管理器开启时,暴力访问可能被禁止,导致运行时错误。
3.基本类型与包装类型区分
反射获取方法时,int.class与Integer.class是不同的类型,不要搞混。
4.方法重载的定位
通过方法名+参数类型列表来定位方法,确保参数类型准确,否则会抛出
NoSuchMethodException。


七、总结

Java反射就像一把双刃剑,用得好能让框架灵活强大,用得不好则可能带来性能问题和维护噩梦。作为开发者,我们需要理解它的原理,知道在什么场景下必须使用,以及如何用得更安全高效。
希望这篇文章能帮你建立对反射的整体认知,并在实际工作中游刃有余。


附:反射性能优化小贴士

  • 对于同一个类得反射操作,将Class、Method等对象缓存起来,避免重复获取。
  • 在需要高性能得场景,可以考虑使用MethodHandle(JDK7+)或Lambda来替代部分反射调用。
  • 框架如Spring会大量使用ReflectionUtils等工具进行反射操作得优化和缓存,我们也可以参考其思想。

更多推荐