前言
前两篇分别讲解了Class和ClassLoader,正因为内存里存在着一份类的描述文件(Class类对象),我们可以依托这个类对象对程序做一些特殊的处理,而反射则是它提供的能力。
通过本篇文章,你将了解到:
- Java类和对象关系
- 什么是反射?
- 反射提供了哪些能力?
- 反射效率低吗?慢在哪?
- 反射的应用场景
1. JAVA类和对象关系

如上图,简述为如下几个步骤:
- 我们在.java文件里编写源码
- 编译后变成.class
- 当启动应用后,需要加载对应的类时,ClassLoader将.class加载到内存,并生成Class对象(同一个ClassLoader加载,全局唯一一份)
- Class对象拥有属性、方法的描述,一个Class可以对应无数个对象(理论可以new出无限个)
定义一个Fish类:
java
public class Fish {
static {
System.out.println("fish class init");
}
String name;
public Fish(String name) {
this.name = name;
}
public void eat()
{
System.out.println("name is:"+name);
}
}
引用该类:
java
public class Main {
public static void main(String[] args){
Fish fish1 = new Fish("fish1");
fish1.eat();
Fish fish2 = new Fish("fish2");
fish2.eat();
}
}
打印如下:
当第一次创建对象:new Fish("fish1")时,会触发ClassLoader加载,生成Class对象存放在内存里。
当第二次创建对象:new Fish("fish2")时,不再触发ClassLoader加载。
2. 什么是反射?
new关键字
没有对象,自力更生,靠自己双手------new 出来。
这是正常的思考逻辑,new关键字创建的对象好处在于:
- 编译时确定类型
- 效率比反射高
- 编译时类型安全检测
- JIT代码优化
因此,绝大部分场景下,我们都是使用new关键字创建对象。
然而这是理想状况,总有事与愿违的时候。
ps:有些文章称new方式是正射,但实际上官方文档并没有明确这一说法。个人猜测是反射出来后,为了区分反射,才有的这定义;你可以称new方式是普通创建对象方式,也可以是正射,怎样好理解就怎样来。
反射访问私有属性/方法
项目里引用了第三方sdk,sdk对外暴露的类和方法如下:
java
package com.fish.reflection;
public class ThirdSDK {
public void initSDK() {
System.out.println("initSDK");
}
private void changeStatus() {
System.out.println("changeStatus");
}
}
项目里引用:
java
public class Main {
public static void main(String[] args) throws Exception {
ThirdSDK thirdSDK = new ThirdSDK();
thirdSDK.initSDK();
//编译报错,没法访问
thirdSDK.changeStatus();
}
}
因为ThirdSDK里的changeStatus是私有方法,外部是没法直接引用的,但我们又想要这个方法,该怎么办呢?
两种方式:
- 让三方库提供者改为public修饰(这不太可能,即使可能周期也比较长)2. 使用反射。
使用反射,只需要添加三行代码:
java
public class Main {
public static void main(String[] args) throws Exception {
ThirdSDK thirdSDK = new ThirdSDK();
thirdSDK.initSDK();
//找到方法
Method method = thirdSDK.getClass().getDeclaredMethod("changeStatus");
//设置访问权限
method.setAccessible(true);
//调用方法
method.invoke(thirdSDK);
}
}
可以看出,在编译期,编译器并不知道你要调用changeStatus()方法,JVM只有在运行时才知道要调用changeStatus()方法。
当然,我们也可以通过反射获取/修改私有属性的值。
反射解耦
有时候SDK里的类并不是public修饰的,如下:
java
package com.fish.reflection;
class Inner {
void eat() {
System.out.println("eat inner");
}
}
想要访问对象方法eat(),第一步得拿到Inner对象,第二步才能调用eat()方法。
使用反射,过程如下:
java
public class Main {
public static void main(String[] args) throws Exception {
//通过全限定类名加载Class
Class<?> classInner = Class.forName("com.fish.reflection.Inner");
//获取构造器
Constructor<?> constructor = classInner.getDeclaredConstructor();
constructor.setAccessible(true);
//调用构造器创建对象
Object object = constructor.newInstance();
//有了对象剩下的就是访问私有方法
Method method = classInner.getDeclaredMethod("eat");
method.setAccessible(true);
method.invoke(object);
}
}
如上,先通过反射创建对象,再通过反射访问私有方法。
可以看出,整个过程都没有涉及到Inner这个类的引用,而是动态创建对象,调用方法。
此种方式广泛用在各种框架,模块之间联系纽带是字符串(类名),没有编译期直接的类型依赖,主要目的用于解耦。
反射定义
反射(Reflection) 是Java语言的一个特性,它允许程序在运行时检查和操作类、接口、字段和方法的信息,而不需要在编译时知道它们的名称。
核心点在于:
运行时检查:在程序执行期间检查类和对象的结构
动态操作:创建对象、调用方法、访问字段等操作
无需编译时信息:不需要在编译时知道具体要操作的类、方法或字段名称
下面类比的例子可能会对反射的理解有些许帮助:
一个景区(类)里有多个景点(属性),同时也会举办一些活动(方法)
游客从正常渠道购票进入景区,景区会附带游览指引,哪个位置有什么景点,什么时间有活动,都有具体的说明,这个时候游客对什么感兴趣直接按照地图过去就可以。因此游客游览的效率很高。
有时候景点在维护不开放(私有),那么游客无法去访问。
游客因为某种原因不从正常渠道购票,而是托关系(反射)进入了景区,此时他只知道有哪些景点和活动,不知道该怎么过去和不知道什么时候举行,于是他只能一个个地去找景点和活动(遍历访问属性和方法),并且还面临工作人员的检查(权限检测),整体效率偏低。
但由于他是托关系进去的,因此他能够去一些不开放的景点和活动区域(访问私有属性/方法),这就体现出了灵活性。
3. 反射提供了哪些能力?
反射的基础--CLASS

哦,不对,是Class满足了我们对类的一切幻想,来看看Class里可以拿到什么。
有如下代码:
java
package com.fish.reflection;
interface IFish {
void eat(String food);
String swim();
}
class Animal {
String name;
public String getName() {
return name;
}
}
public class Fish extends Animal implements IFish {
//鲤鱼
static String category = "carp";
static {
System.out.println("fish class init");
}
public String name;
int age = 0;
public Fish() {
this.name = "default";
}
public Fish(String name) {
this.name = name;
}
private Fish(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String getName() {
return name;
}
public static String getCategory(long id) {
System.out.println("getCategory:" + id);
return category;
}
private void changeAge(int age) {
this.age = age;
}
public void eat(String food) {
System.out.println("fish eat " + food);
}
public String swim() {
System.out.println("fish swim");
return "fish swim";
}
}
那么Fish.Class 对象里能拿到信息如下:
- Fish继承的父类、实现的接口
- Fish类的名字(简称和全称)
- Fish类的所有方法(公开/私有、静态/非静态)
- Fish类的所有属性(公开/私有、静态/非静态)
- Fish类的所有构造函数(公开/私有)
- Fish类的注解
- 其它的信息
拿到类里的方法/属性后,我们就可以调用方法和访问属性。
拿到类里的构造函数后,我们就可以创建对象。
因此Class是反射的基础。
那么,怎样获取一个Class对象呢?
有以下三种方式:
第一种:通过对象(object.getClass)获取
java
Fish fish = new Fish();
Class<?> fishClass = fish.getClass();
此种方式需要能够访问类,并且先通过具体类new出对象。
第二种:通过类.class获取
java
Class<?> fishClass1 = Fish.class;
此种方式需要能够访问类。
第三种方式:通过全限定类名获取
java
Class<?> fishClass2 = Class.forName("com.fish.reflection.Fish");
此种方式是完全解耦,只通过类名即可生成Class。
Class.forName先调用到C++层,最终由返回到Java层的loadClass()进行类加载,最终创建Class。
反射常用的API
以上面的Fish.java为例,讲解常用的API使用方式。
如上图,主要分析访问属性和调用方法,访问注解下篇重点分析。
调用构造函数
java
public class Main {
public static void main(String[] args) throws Exception {
Class<?> fishClass = Class.forName("com.fish.reflection.Fish");
//创建对象
Constructor<?> constructor = fishClass.getDeclaredConstructor();
//只是通过反射创建了构造函数并调用,调用普通方法eat没有用反射
Fish fish = (Fish)constructor.newInstance();
fish.eat("bug");
//访问不了Fish,用如下方式
Object object = constructor.newInstance();
Method eat = fishClass.getMethod("eat", String.class);
eat.invoke(object, "bug2");
//获取所有的public的构造方法
Constructor<?>[] constructors = fishClass.getConstructors();
for (Constructor<?> constructor1 : constructors) {
System.out.println("getConstructors:" + constructor1);
}
//获取所有声明的构造方法
Constructor<?>[] declaredConstructors = fishClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("getDeclaredConstructors:" + declaredConstructor);
}
}
}
打印结果:
调用普通方法
java
public class Main {
public static void main(String[] args) throws Exception {
Class<?> fishClass = Class.forName("com.fish.reflection.Fish");
//创建对象
Constructor<?> constructor = fishClass.getDeclaredConstructor();
Object object = constructor.newInstance();
//调用普通-对象成员-公有方法
Method method = fishClass.getMethod("swim");
String result = (String)method.invoke(object);
System.out.println(result);
//调用普通-对象成员-私有方法
Method method1 = fishClass.getDeclaredMethod("changeAge", int.class);
//私有方法需要设置 accessible 为 true
method1.setAccessible(true);
method1.invoke(object, 333);
//调用普通-类成员-公有方法
Method method2 = fishClass.getMethod("getCategory", long.class);
String result2 = (String)method2.invoke(null, 44);
System.out.println(result2);
}
}
打印如下:
访问属性
java
public class Main {
public static void main(String[] args) throws Exception {
Class<?> fishClass = Class.forName("com.fish.reflection.Fish");
//创建对象
Constructor<?> constructor = fishClass.getDeclaredConstructor();
Object object = constructor.newInstance();
//调用普通-对象-公有属性
Field field = fishClass.getField("name");
field.set(object, "fish-set-public");
System.out.println(field.get(object));
//调用普通-对象成员-私有属性
Field field1 = fishClass.getDeclaredField("age");
//设置私有属性可访问
field1.setAccessible(true);
field1.set(object, 18);
System.out.println(field1.get(object));
//调用普通-类属性-私有属性
Field field2 = fishClass.getDeclaredField("category");
field2.setAccessible(true);
field2.set(null, "fish-set-static");
System.out.println(field2.get(null));
}
}
打印如下:
4. 反射效率低吗?慢在哪?
你也许在不同的文章里看到不少说反射效率低的说法,那么它真的低吗?
使用如下代码进行测试:
java
public class Main {
public static void main(String[] args) throws Exception {
Class<?> fishClass = Class.forName("com.fish.reflection.Fish");
long starTime = System.currentTimeMillis();
for(int i = 0; i < 10000; i++) {
Fish fish = new Fish();
}
System.out.println("普通创建对象花费:cost:" + (System.currentTimeMillis() - starTime) + "毫秒");
long starTime1 = System.currentTimeMillis();
for(int i = 0; i < 10000; i++) {
Constructor<?> constructor = fishClass.getDeclaredConstructor();
Object object = constructor.newInstance();
}
System.out.println("反射创建对象花费:cost:" + (System.currentTimeMillis() - starTime1) + "毫秒");
long starTime2 = System.currentTimeMillis();
for(int i = 0; i < 10000; i++) {
Fish fish = new Fish();
fish.eat("fish");
}
System.out.println("普通创建对象-调用方法花费:cost:" + (System.currentTimeMillis() - starTime2) + "毫秒");
long starTime3 = System.currentTimeMillis();
for(int i = 0; i < 10000; i++) {
Constructor<?> constructor = fishClass.getDeclaredConstructor();
Object object = constructor.newInstance();
Method method1 = fishClass.getDeclaredMethod("changeAge", int.class);
//私有方法需要设置 accessible 为 true
method1.setAccessible(true);
method1.invoke(object, 333);
}
System.out.println("反射创建对象-调用方法花费:cost:" + (System.currentTimeMillis() - starTime3) + "毫秒");
}
}
普通的对象创建和反射对象创建对比,普通方法调用和反射方法调用对比。
结果如下:
明显可以看出,反射是比普通方式耗时,主要耗时在获取方法和属性时需要遍历,同时私有的方法/属性需要设置权限绕过。
但差距并不大,当然你可以继续加大循环次数测试,也许对比会更明显。
每一代的JVM可能会对反射的效率有优化,不同的应用场景对反射性能敏感度不一样,因此具体情况具体分析,你觉得反射影响了你的性能,那么可以考虑放到其它线程或者不使用它(比如Android设备上早期性能较差,反射如果在主线程执行会影响UI渲染)。
反射依旧是解耦的利器,用得巧妙将会事半功倍。
5. 反射的应用场景
1. 框架开发
依赖注入框架:如Spring框架使用反射来实现依赖注入,通过反射获取注解信息并实例化对象
ORM框架:如Hibernate、MyBatis等框架使用反射来映射数据库表与Java对象
Web框架:如Spring MVC使用反射处理控制器方法的调用
2. 配置文件解析
XML配置处理:通过反射解析XML配置文件,动态创建和配置对象
注解处理:解析类、方法、字段上的注解信息,实现各种功能
3. 动态代理
AOP实现:Spring AOP使用Java动态代理创建代理对象
RPC框架:远程方法调用中动态生成代理类
4. 序列化和反序列化
JSON处理:Jackson、Gson等库使用反射将Java对象转换为JSON格式
对象持久化:将对象状态保存到文件或数据库中
5. 其它
如果本文对你有帮助,请一键三连~
下篇将分析反射的另一个框架里的高频应用:注解,敬请期待~