目录
  1. 1. 一、注解
    1. 1.1. 1.1 概述
    2. 1.2. 1.2 注解声明
    3. 1.3. 1.3 元注解
      1. 1.3.1. @Target
      2. 1.3.2. @Retention
    4. 1.4. 1.4 注解类型元素
    5. 1.5. 1.5 注解应用场景
      1. 1.5.1. RUNTIME
      2. 1.5.2. CLASS
      3. 1.5.3. SOURCE
  2. 2. 二、反射
    1. 2.1. 2.1 概述
    2. 2.2. 2.2 Class
      1. 2.2.1. 获得 Class 对象
        1. 2.2.1.1. Class.forName vs ClassLoader.loadClass
      2. 2.2.2. 创建实例
      3. 2.2.3. 获取构造器信息
      4. 2.2.4. 获取类的成员变量(字段)信息
      5. 2.2.5. 调用方法
    3. 2.3. 2.3 setAccessible 与安全检查
  3. 3. 三、利用反射创建数组
  4. 4. 四、反射获取泛型的真实类型
    1. 4.1. TypeVariable
    2. 4.2. ParameterizedType
    3. 4.3. GenericArrayType
    4. 4.4. WildcardType
  5. 5. 五、反射的性能开销与优化
    1. 5.1. 5.1 反射性能开销的来源
    2. 5.2. 5.2 Inflation 机制
    3. 5.3. 5.3 反射优化最佳实践
  6. 6. 六、Android 中的反射限制
    1. 6.1. 6.1 Android 隐藏 API 限制
    2. 6.2. 6.2 绕过隐藏 API 限制的技术
  7. 7. 七、实战:Gson 反序列化
  8. 8. 八、动态代理
  9. 9. 九、面试常问题目
Java进阶之反射

一、注解

1.1 概述

Java 注解(Annotation)又称 Java 标注,是 JDK 5.0 引入的一种注释机制。注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。因此注解对其注解的代码没有直接影响。

1.2 注解声明

Java 中所有的注解,默认实现 Annotation 接口:

package java.lang.annotation;

public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}

注解的声明使用 @interface 关键字:

public @interface XXXXX {
// 注解元素
}

1.3 元注解

在定义注解时,注解类也能够使用其他注解声明。对注解类型进行注解的注解类,亦即注解上面的注解,我们称之为”meta-annotation”(元注解)。一般需要指定的元注解有两个:

@Target

注解标记另一个注解,从而限制注解的 Java 元素类型:

  • ElementType.ANNOTATION_TYPE — 应用注解类型
  • ElementType.CONSTRUCTOR — 应用构造函数
  • ElementType.FIELD — 应用字段或属性
  • ElementType.LOCAL_VARIABLE — 应用局部变量
  • ElementType.METHOD — 应用于方法
  • ElementType.PACKAGE — 应用于包声明
  • ElementType.PARAMETER — 应用于方法的参数
  • ElementType.TYPE — 应用于类的任何元素(只能注解类、接口、枚举)

@Retention

注解指定标记注解保留方式:

  • RetentionPolicy.SOURCE — 标记的注解仅保留在源级别中,并被编译器忽略
  • RetentionPolicy.CLASS — 标记的注解在编译时由编译器保留,但 JVM 会忽略
  • RetentionPolicy.RUNTIME — 标记的注解由 JVM 保留,因此运行时环境可以使用

1.4 注解类型元素

在元注解中,允许使用注解时传递参数,同时也能让自定义注解的主体包含 annotation type element(注解类型元素):

@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface LeoCheung {
String value(); // 无默认值
int age() default 1; // 有默认值
}

注意:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值

@LeoCheung("干爆弟弟们")  // 如果只存在 value 元素需要传值,则可以省略元素名
@LeoCheung(value = "阿里我来啦", age = 18)

1.5 注解应用场景

RUNTIME

注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。

CLASS

注解会保留在 class 文件中,但是会被虚拟机忽略(即无法在运行期反射获取注解)。这种注解的应用场景为字节码操作,如:AspectJ、热修复 Robust。

如果我们使用普通的编程方式,需要在代码中疯狂进行 if-else 判断,如果存在十处就需要在这十个判断点加入校验判断。此时,我们可以借助 AOP(面向切面) 编程思想,将程序中所有功能点划分为:需要登录和无需登录两种类型,即两个切面。对于切面的切分即可采用注解。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Login { }

@Login
public void jumpA() {
startActivity(new Intent(this, AActivity.class));
}

public void jumpB() {
startActivity(new Intent(this, BActivity.class));
}

在操作字节码时,就能够根据方法是否具备该注解来修改 class 中该方法的内容加入 if-else 的代码段:

// Class 字节码被修改后
@Login
public void jumpA() {
if (this.isLogin) {
this.startActivity(new Intent(this, LoginActivity.class));
} else {
this.startActivity(new Intent(this, AActivity.class));
}
}

SOURCE

IDE 语法检查:在 Android 开发中,support-annotations 与 androidx.annotation 中均有提供 @IntDef 注解:

@Retention(SOURCE)
@Target(ANNOTATION_TYPE)
public @interface IntDef {
int[] value() default {};
boolean flag() default false;
boolean open() default false;
}

Java 中 Enum(枚举)的实质是特殊单例的静态成员变量,在运行期所有枚举类作为单例全部加载到内存中,比常量多 5 到 10 倍的内存占用。此注解的意义在于能够取代枚举,实现如方法入参限制。

APT 注解处理器:APT 全称为 “Annotation Processor Tools”,意为注解处理器。编写好的 Java 源文件需要经过 javac 的编译,翻译为虚拟机能够加载解析的字节码 Class 文件。注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。

注解处理器是对注解应用最为广泛的场景,在 Glide、EventBus3、ButterKnife、Tinker、ARouter 等常用框架中都有注解处理器的身影。你可能发现这些框架中对注解的定义并不是 SOURCE 级别,更多的是 CLASS 级别。CLASS 包含了 SOURCE,RUNTIME 包含 SOURCE、CLASS。

二、反射

2.1 概述

一般情况下,我们使用的某个类时必定知道它是什么类,是用来做什么的,并且能够获得此类的引用。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

而反射不一样,它应用于一开始并不知道自己要初始化的类对象是什么情形下,自然也无法使用 new 关键字来创建对象。此时,我们可以使用 JDK 提供的反射 API 进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,并且能够改变它的属性。 这也是 Java 被视为动态语言的关键。

Java 反射机制提供了以下功能:

  • 在运行时构造任意一个类的对象
  • 在运行时获取或者修改任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法

2.2 Class

反射始于 Class,Class 是一个类,封装了当前对象所对应的类的信息。 Class 类是一个对象镜面映射出的结果,对象可以在 Class 这面镜子里看到自己的属性、方法、构造器,实现了哪些接口等等,即使是私有的,藏在内心处的,也可以通过手段查到。对于每个类而言,JRE 都为其保留了一个不变的 Class 类型的对象,同样,一个类在 JVM 中只会有一个 Class 类。

获得 Class 对象

获取 Class 对象的三种方法:

  1. 通过类名获取:类名.class
  2. 通过对象获取:对象名.getClass()
  3. 通过全类名获取:Class.forName(全类名)classLoader.loadClass(全类名)
Class.forName vs ClassLoader.loadClass
// Class.forName: 加载 + 链接 + 初始化(执行 static 代码块)
Class<?> clazz1 = Class.forName("com.example.MyClass"); // 默认 initialize = true
Class<?> clazz2 = Class.forName("com.example.MyClass",
false, classLoader); // initialize = false,只加载不初始化

// ClassLoader.loadClass: 只加载,不初始化
Class<?> clazz3 = classLoader.loadClass("com.example.MyClass");
// 不执行 static 代码块,不触发类初始化

// 典型应用:JDBC 驱动加载
// Class.forName("com.mysql.cj.jdbc.Driver");
// 这会执行 Driver 类的 static 代码块,完成驱动注册
// 而 loadClass 不会执行 static 代码块,驱动无法注册

创建实例

通过反射来生成对象主要有两种方式:

  1. 使用 Class 对象的 newInstance() 方法来创建 Class 对象对应类的实例:
Class<?> cls = Integer.class;
Object i = cls.newInstance(); // 调用无参构造器(Java 9+ 已废弃,推荐使用 getDeclaredConstructor().newInstance())
  1. 先通过 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance 方法来构建对象:
Class<?> cls = String.class;
Constructor constructor = cls.getConstructor(String.class);
Object obj = constructor.newInstance("加油啊夏目");

获取构造器信息

Constructor getConstructor(Class[] params)              — 获得指定参数类型的 public 构造函数(包括父类)
Constructor[] getConstructors() — 获得类的所有 public 构造函数
Constructor getDeclaredConstructor(Class[] params) — 获得指定参数类型的构造函数(包括私有)
Constructor[] getDeclaredConstructors() — 获得类的所有构造函数(与接入级别无关)

获取类的成员变量(字段)信息

Field getField(String name)                    — 获得指定名称的 public 字段
Field[] getFields() — 获得类的所有 public 字段
Field getDeclaredField(String name) — 获得类声明的指定名称字段(包括私有)
Field[] getDeclaredFields() — 获得类声明的所有字段

调用方法

Method getMethod(String name, Class[] params)           — 使用特定的参数类型,获得命名的 public 方法
Method[] getMethods() — 获得类的所有 public 方法
Method getDeclaredMethod(String name, Class[] params) — 使用特定的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() — 获得类声明的所有方法(包括私有)

当我们从类中获取一个方法之后,就可以使用 invoke() 方法来调用这个方法:

public Object invoke(Object obj, Object... args)

2.3 setAccessible 与安全检查

Method method = SomeClass.class.getDeclaredMethod("privateMethod");
// 默认情况下,访问私有方法会抛 IllegalAccessException
method.setAccessible(true); // 绕过 Java 访问控制检查
method.invoke(instance);

// setAccessible 的实现:
// 1. 检查调用者是否有权限(Reflection.getCallerClass())
// 2. 如果有权限,设置 override 标志为 true
// 3. 后续调用 invocation 时跳过访问控制检查

// 性能影响:首次调用 setAccessible 较慢(需要做安全检查)
// 但后续调用不受影响

在 Java 9+ 中,模块系统引入了严格的封装限制。反射访问内部 API 可能触发 InaccessibleObjectException,需要通过 --add-opens JVM 参数显式开放。

三、利用反射创建数组

数组在 Java 里是比较特殊的一种类型,它可以赋值给一个 Object Reference,其中的 Array 类为 java.lang.reflect.Array 类。通过 Array.newInstance() 创建数组对象:

public static Object newInstance(Class<?> componentType, int length);
// 通过反射创建数组示例
int[] arr = (int[]) Array.newInstance(int.class, 10);

// 通过反射设置和获取数组元素
Array.set(arr, 0, 42);
int value = Array.getInt(arr, 0); // 42

// 多维数组
int[][] matrix = (int[][]) Array.newInstance(int.class, 3, 4);

四、反射获取泛型的真实类型

当我们对一个泛型类进行反射时,需要得到泛型中的真实数据类型,来完成比如 json 反序列化的操作。此时需要通过 Type 体系来完成。Type 接口包含了一个实现类(Class)和四个实现接口:

  • TypeVariable:泛型类型变量。可以获取泛型上下限等信息。
  • ParameterizedType:具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)。
  • GenericArrayType:当需要描述的类型是泛型类的数组时,比如 List[],Map[],此接口会作为 Type 的实现。
  • WildcardType:通配符泛型,获得上下限信息。

TypeVariable

public class TestType <K extends Comparable & Serializable, V> {
K key;
V value;

public static void main(String[] args) throws Exception {
Field fk = TestType.class.getDeclaredField("key");
Field fv = TestType.class.getDeclaredField("value");

TypeVariable keyType = (TypeVariable) fk.getGenericType();
TypeVariable valueType = (TypeVariable) fv.getGenericType();

// getName 方法
System.out.println(keyType.getName()); // K
System.out.println(valueType.getName()); // V

// getGenericDeclaration 方法
System.out.println(keyType.getGenericDeclaration()); // class com.test.TestType

// getBounds 方法
System.out.println("K 的上界:");
for (Type type : keyType.getBounds()) {
System.out.println(type); // interface java.lang.Comparable, interface java.io.Serializable
}

System.out.println("V 的上界:"); // 没明确声明上界的,默认上界是 Object
for (Type type : valueType.getBounds()) {
System.out.println(type); // class java.lang.Object
}
}
}

ParameterizedType

public class TestType {
Map<String, String> map;

public static void main(String[] args) throws Exception {
Field f = TestType.class.getDeclaredField("map");
System.out.println(f.getGenericType()); // java.util.Map<java.lang.String, java.lang.String>

ParameterizedType pType = (ParameterizedType) f.getGenericType();
System.out.println(pType.getRawType()); // interface java.util.Map

for (Type type : pType.getActualTypeArguments()) {
System.out.println(type); // 打印两遍: class java.lang.String
}
}
}

GenericArrayType

public class TestType<T> {
List<String>[] lists;

public static void main(String[] args) throws Exception {
Field f = TestType.class.getDeclaredField("lists");
GenericArrayType genericType = (GenericArrayType) f.getGenericType();
System.out.println(genericType.getGenericComponentType());
// 输出: java.util.List<java.lang.String>
}
}

WildcardType

public class TestType {
private List<? extends Number> a; // 上限
private List<? super String> b; // 下限

public static void main(String[] args) throws Exception {
Field fieldA = TestType.class.getDeclaredField("a");
Field fieldB = TestType.class.getDeclaredField("b");

// 先拿到泛型类型
ParameterizedType pTypeA = (ParameterizedType) fieldA.getGenericType();
ParameterizedType pTypeB = (ParameterizedType) fieldB.getGenericType();

// 再从泛型里拿到通配符类型
WildcardType wTypeA = (WildcardType) pTypeA.getActualTypeArguments()[0];
WildcardType wTypeB = (WildcardType) pTypeB.getActualTypeArguments()[0];

// 再取边界
System.out.println(wTypeA.getUpperBounds()[0]); // class java.lang.Number
System.out.println(wTypeB.getLowerBounds()[0]); // class java.lang.String

// 查看通配符类型
System.out.println(wTypeA); // ? extends java.lang.Number
}
}

五、反射的性能开销与优化

5.1 反射性能开销的来源

反射比直接调用慢,主要开销来自:

  1. 类型安全检查:每次调用 invoke 都需要检查访问权限、参数类型匹配。
  2. 参数装箱/拆箱:invoke 的参数和返回值都是 Object 类型,基本类型需要装箱/拆箱。
  3. 方法内联限制:JIT 编译器无法内联反射调用的方法。
  4. 可见性检查:setAccessible 绕过访问控制,但 JVM 仍需要验证。

5.2 Inflation 机制

// JVM 对反射有优化——Inflation(膨胀)机制
// 前 15 次(-Dsun.reflect.inflationThreshold=15)使用 JNI 调用
// 之后生成字节码(MethodAccessor),性能接近直接调用

// Method.invoke 的内部路径:
// Method.invoke()
// → MethodAccessor.invoke()
// → NativeMethodAccessorImpl.invoke() — 前 15 次(JNI)
// → GeneratedMethodAccessor1.invoke() — 第 16 次起(字节码生成)
//
// GeneratedMethodAccessor 使用 ASM 动态生成类:
// public Object invoke(Object obj, Object[] args) {
// return ((TargetClass) obj).targetMethod((String) args[0], (int) args[1]);
// }
// 这就是为什么多次反射调用的性能与直接调用差别不大的原因

5.3 反射优化最佳实践

// 1. 缓存 Method/Field/Constructor 对象
// BAD:每次都查找
for (int i = 0; i < 10000; i++) {
Method m = obj.getClass().getMethod("getName");
m.invoke(obj);
}

// GOOD:缓存 Method 对象
Method m = obj.getClass().getMethod("getName");
for (int i = 0; i < 10000; i++) {
m.invoke(obj);
}

// 2. 使用 setAccessible(true)(一次性开销,后续调用快)
m.setAccessible(true); // 在循环外调用

// 3. 优先使用 MethodHandle(Java 7+)
// MethodHandle 是 JVM 级别的函数指针,比反射更高效
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(String.class); // 返回类型
MethodHandle mh = lookup.findVirtual(String.class, "toString", mt);
String result = (String) mh.invoke("hello");

// 4. 对于频繁调用,考虑使用 LambdaMetafactory
// 将 MethodHandle 转换为函数式接口实例
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.findVirtual(MyClass.class, "getName",
MethodType.methodType(String.class));
Function<MyClass, String> getter = (Function<MyClass, String>)
LambdaMetafactory.metafactory(
lookup, "apply",
MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class),
mh, MethodType.methodType(String.class, MyClass.class)
).getTarget().invokeExact();
// 现在 getter.apply(obj) 的性能与直接调用 obj.getName() 几乎相同

六、Android 中的反射限制

6.1 Android 隐藏 API 限制

从 Android 9.0 (API 28) 开始,Google 引入了对隐藏 API(非 SDK 接口)的访问限制:

API 级别分类:
- Whitelist(白名单):SDK 公开 API,可自由访问
- Greylist(灰名单):尚未有公开替代的 API
- greylist:所有应用可访问(可能会在未来的 Android 版本中被限制)
- greylist-max-o:targetSdkVersion <= 26 的应用可访问
- greylist-max-p:targetSdkVersion <= 27 的应用可访问
- Blacklist(黑名单):完全禁止访问,即使使用反射

从 Android 10 (API 29) 开始,所有灰名单 API 的访问都会触发警告或异常。
// 访问隐藏 API 时的典型异常
// java.lang.NoSuchMethodException: android.app.ActivityThread.currentActivityThread []
// 或
// java.lang.NoSuchFieldException: No field mHiddenApiWarning in class ...

6.2 绕过隐藏 API 限制的技术

// 方法 1: 使用元反射(meta-reflection)——反射"反射本身"
// 获取 Class.getDeclaredMethod 方法,然后调用它
Method getDeclaredMethod = Class.class.getDeclaredMethod(
"getDeclaredMethod", String.class, Class[].class);
// 这绕过了 framework 层的检查,因为检查在 ART 层

// 方法 2: 使用双反射(Dual Reflection)/ 系统 ClassLoader
// 利用 Bootstrap ClassLoader 加载的系统类不受限制
// 通过 Class.forName 使用系统 ClassLoader 加载类

// 方法 3: native 层访问(JNI)
// 在 native 层可以直接访问 ART 内部结构
// 使用 JNI 的 FindClass + GetFieldID + GetStaticFieldID

// 方法 4: FreeReflection / RestrictionBypass 等开源库
// 利用 Android 源码中的特殊路径(如 SystemProperties 之类)

// 注意:这些方法随 Android 版本升级可能失效
// Google 在持续加强隐藏 API 限制

七、实战:Gson 反序列化

static class Response<T> {
T data;
int code;
String message;

@Override
public String toString() {
return "Response{" +
"data=" + data +
", code=" + code +
", message='" + message + '\'' + '}';
}

public Response(T data, int code, String message) {
this.data = data;
this.code = code;
this.message = message;
}
}

static class Data {
String result;

public Data(String result) {
this.result = result;
}

@Override
public String toString() {
return "Data{" + "result=" + result + '}';
}
}

public static void main(String[] args) {
Response<Data> dataResponse = new Response(new Data("数据"), 1, "成功");
Gson gson = new Gson();
String json = gson.toJson(dataResponse);
System.out.println(json);

// 为什么 TypeToken 要定义为抽象类?
Response<Data> resp = gson.fromJson(json, new TypeToken<Response<Data>>() {
}.getType());

System.out.println(resp.data.result);
}

在进行 Gson 反序列化时,存在泛型时,可以借助 TypeToken 获取 Type 以完成类型的反序列化。为什么 TypeToken 要被定义成抽象类呢? 因为只有定义为抽象类或者接口,才能在使用中,对需要的实体类进行相对应的创建,此时确定泛型类型,编译才能够将泛型的签名信息正确地记录到 Class 元数据中——即通过 getClass().getGenericSuperclass() 获取父类(TypeToken<Response>)的 ParameterizedType,从中提取 Response<Data> 的实际类型实参。

八、动态代理

Java 动态代理是反射的重要应用之一:

// JDK 动态代理
// 基于接口的代理,使用 Proxy.newProxyInstance + InvocationHandler
interface ApiService {
@GET("/users/{id}")
User getUser(@Path("id") int id);
}

// Retrofit 的核心原理就是动态代理
// 运行时生成的代理类:
// public class $Proxy0 extends Proxy implements ApiService {
// public User getUser(int id) {
// return (User) handler.invoke(this, m3, new Object[]{id});
// }
// }

InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// 解析方法上的注解 → 构建 HTTP 请求 → 执行请求 → 解析响应
return executeRequest(method, args);
}
};

ApiService service = (ApiService) Proxy.newProxyInstance(
ApiService.class.getClassLoader(),
new Class<?>[]{ApiService.class},
handler
);
User user = service.getUser(123); // 实际通过代理执行 HTTP 请求

九、面试常问题目

Q1: Class.forName 和 ClassLoader.loadClass 有什么区别?

Class.forName 默认会完成类的加载、链接和初始化(执行 static 代码块),也可以指定 initialize=false 跳初始化。ClassLoader.loadClass 只加载类,不进行初始化和链接(在首次使用时才初始化)。典型应用:JDBC 驱动加载使用 Class.forName 触发 static 代码块注册驱动。在需要延迟类初始化的场景(如 Spring 的懒加载)使用 loadClass。

Q2: 反射的性能为什么差?有什么优化手段?

反射性能开销来自:(1) 需要做类型安全检查和方法查找;(2) 参数和返回值需要装箱/拆箱;(3) JIT 无法内联反射调用;(4) 首次反射调用需要 JNI 查找。优化手段:(1) 缓存 Method/Field/Constructor 对象;(2) 调用 setAccessible(true) 禁用安全检查;(3) 利用 JVM 的 Inflation 机制(15 次后自动生成字节码加速);(4) 使用 MethodHandle 或 LambdaMetafactory 替代频繁的反射调用;(5) 对于已知类型的频繁调用,将 MethodHandle 转为 Lambda,性能接近直接调用。

Q3: Android 的隐藏 API 限制是什么?如何实现双反射绕过?

从 Android 9.0 开始,Google 限制了对隐藏 API(@hide 标记的 SDK 内部接口)的访问。通过反射访问受限 API 时会抛出 NoSuchMethodException 或 NoSuchFieldException。双反射(Dual Reflection)的绕过原理:先反射获取 Class.getDeclaredMethod 等底层方法本身,然后通过获取到的”元方法”来搜索受限方法——由于 ART 层面的检查在某些版本中有漏洞,使用元反射可以绕过。但这种方法在每个 Android 新版本中可能失效,Google 在持续加固限制。

Q4: 什么是反射的 Inflation 机制?它是如何提高反射性能的?

Inflation 是 JVM 对反射调用的优化。前 15 次反射调用使用 NativeMethodAccessor(通过 JNI 调用),速度较慢。从第 16 次起,JVM 通过字节码生成工具(如 ASM)动态生成 GeneratedMethodAccessor 类,该类将反射调用转换为虚拟调用(virtual dispatch),使 JIT 编译器可以对其应用常规优化(如内联)。因此反射调用的”预热”很重要——对于需要频繁反射调用的热点路径,前 15 次调用后性能会显著提升。

Q5: MethodHandle 和反射(Method.invoke)有什么区别?什么时候应该使用 MethodHandle?

MethodHandle 是 Java 7 引入的 JVM 级函数指针,与反射的主要区别:(1) MethodHandle 在创建时就完成了类型链接和访问检查(而非每次调用时检查),invokeExact 调用几乎零开销;(2) MethodHandle 支持方法句柄变换(MethodHandles.filterArguments、guardWithTest 等),可以在 MethodHandle 层面组合调用逻辑;(3) MethodHandle 可以被 JIT 内联(特别是常量 MethodHandle);(4) MethodHandle 的签名在创建时确定,调用时严格类型匹配(invokeExact)。使用建议:对于热点路径的频繁调用,优先使用 MethodHandle + LambdaMetafactory;对于通用场景(工具类、框架)使用反射更灵活。


参考源码路径:

  • java.lang.reflect 包:$JAVA_HOME/src/java.base/share/classes/java/lang/reflect/
  • ART 反射实现:art/runtime/reflection.cc
  • 隐藏 API 限制:art/runtime/hidden_api.h
  • Retrofit 动态代理:https://github.com/square/retrofit
打赏
  • 微信
  • 支付宝

评论