如何通过反射从静态方法中获取 Predicate?
How can I get Predicate from static methods via reflection?
给定一个由静态谓词方法组成的 class,我想通过反射访问它们并将它们转换为 Predicate<Object>
类型。
public class MyPredicates {
public static boolean isNull(Object obj) {
return obj == null;
}
public static boolean hasNegativeHashcode(Object obj) {
return obj.hashCode() < 0;
}
}
通常,我会写下面的代码来获取谓词:
Predicate<Object> isNull = MyPredicates::isNull;
但是,我不知道如何使用反射来做到这一点。我的目的是为这些方法创建一个注释并通过反射获取它们,从而为我的框架创建一个可用谓词列表。
我想到了三种可能的方法,none 我觉得其中一种不错。
- 我可以像
Method
那样保留它,调用 invoke()
并将返回的 Object
转换为 boolean
。然而,这将很难阅读,这意味着我无法在运行时检查类型。
- 我可以将反射调用包装到
Predicate
但这会涉及额外的开销。
- 我可以让用户分别注册每个方法(当adding/removing很多方法时很难维护)。
无论如何,我担心直接使用反射会增加更多开销并减慢程序速度。
所以,我的问题是:
- 我可以直接通过反射获取
Predicate
吗?
- 如果不是,在不增加太多开销的情况下访问此类方法的适当方式是什么,同时具有可用的 API(例如通过涉及
Predicate
)?
TL;DR: Return Predicate
直接来自静态方法并将其存储以备将来使用(可能在以方法名称作为键的映射中) 以消除反射访问的速度瓶颈。
public static Predicate<Object> isNull() {
return obj -> obj == null;
}
首先,了解JVM 如何处理方法引用很重要。有很棒的 explanation of that in another question. Take a look at the lambda translation document - 特别是 方法引用捕获部分 .
list.filter(String::isEmpty)
is translated as
list.filter(indy(MH(metaFactory), MH(invokeVirtual Predicate.apply), MH(invokeVirtual String.isEmpty))()))
这意味着 Predicate
在 运行 时间内不存在,除非您将其写入代码。可能有一种方便的方法可以通过反射 API 获取它,但据我所知,没有。您可以使用 dynamic proxies 编写类似的内容;但是,我认为这会给代码增加不必要的复杂性。
这是实现所需行为的 4 种不同方法的基准,包括问题中提到的方法:
Benchmark Mode Cnt Score Error Units
MyBenchmark.testNormal thrpt 30 362.782 ± 13.521 ops/us
MyBenchmark.testReflectionInvoke thrpt 30 60.440 ± 1.394 ops/us
MyBenchmark.testReflectionWrappedPredicate thrpt 30 62.948 ± 0.846 ops/us
MyBenchmark.testReflectionReturnedPredicate thrpt 30 381.780 ± 5.265 ops/us
- testNormal 通过
::
operator 访问谓词
- testReflectionInvoke直接使用
invoke()
方法
- testReflectionWrappedPredicate 将
invoke()
方法包装到 Precicate
- testReflectionReturnedPredicate 使用了 returns
Predicate
的方法,所以只调用一次反射
出于此示例的目的缓存了谓词,因为我们没有测试获取谓词的速度,而是假设它只会完成一次。
从结果可以看出,反射比正常访问谓词慢6倍。因此,在我看来,cleanest/most 可维护的方法是使用不带参数的方法 return Predicate
类型 而不是 boolean
.
public static Predicate<Object> isNull() {
return obj -> obj == null;
}
已使用 3 次分叉、4 次预热迭代和 10 次迭代完成基准测试。
这是我用来测试的代码,如果你想运行自己做基准测试(使用JMH框架做基准测试):
public class MyBenchmark {
public static Predicate<Object> normal = MyBenchmark::isNull;
public static Method reflectionInvoke;
public static Predicate<Object> reflectionWrappedPredicate;
public static Predicate<Object> reflectionReturnedPredicate;
static {
try {
Method m1 = MyBenchmark.class.getMethod("isNull", Object.class);
reflectionInvoke = m1;
reflectionWrappedPredicate = a -> {
try {
return (boolean)m1.invoke(null, a);
}
catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
return false;
}
};
Method m2 = MyBenchmark.class.getMethod("isNull");
reflectionReturnedPredicate = (Predicate)m2.invoke(null);
}
catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
ex.printStackTrace();
};
}
@Benchmark
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testNormal() {
Predicate<Object> p = normal;
return p.test(this) | p.test(null);
}
@Benchmark
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testReflectionInvoke() {
try {
Method m = reflectionInvoke;
return (boolean)m.invoke(null, this) | (boolean)m.invoke(null, (Object)null);
}
catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException ex) {
ex.printStackTrace();
return false;
}
}
@Benchmark
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testReflectionWrappedPredicate() {
Predicate<Object> p = reflectionWrappedPredicate;
return p.test(this) | p.test(null);
}
@Benchmark
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testReflectionReturnedPredicate() {
Predicate<Object> p = reflectionReturnedPredicate;
return p.test(this) | p.test(null);
}
public static boolean isNull(Object obj) {
return obj == null;
}
public static Predicate<Object> isNull() {
return obj -> obj == null;
}
}
给定一个由静态谓词方法组成的 class,我想通过反射访问它们并将它们转换为 Predicate<Object>
类型。
public class MyPredicates {
public static boolean isNull(Object obj) {
return obj == null;
}
public static boolean hasNegativeHashcode(Object obj) {
return obj.hashCode() < 0;
}
}
通常,我会写下面的代码来获取谓词:
Predicate<Object> isNull = MyPredicates::isNull;
但是,我不知道如何使用反射来做到这一点。我的目的是为这些方法创建一个注释并通过反射获取它们,从而为我的框架创建一个可用谓词列表。
我想到了三种可能的方法,none 我觉得其中一种不错。
- 我可以像
Method
那样保留它,调用invoke()
并将返回的Object
转换为boolean
。然而,这将很难阅读,这意味着我无法在运行时检查类型。 - 我可以将反射调用包装到
Predicate
但这会涉及额外的开销。 - 我可以让用户分别注册每个方法(当adding/removing很多方法时很难维护)。
无论如何,我担心直接使用反射会增加更多开销并减慢程序速度。
所以,我的问题是:
- 我可以直接通过反射获取
Predicate
吗? - 如果不是,在不增加太多开销的情况下访问此类方法的适当方式是什么,同时具有可用的 API(例如通过涉及
Predicate
)?
TL;DR: Return Predicate
直接来自静态方法并将其存储以备将来使用(可能在以方法名称作为键的映射中) 以消除反射访问的速度瓶颈。
public static Predicate<Object> isNull() {
return obj -> obj == null;
}
首先,了解JVM 如何处理方法引用很重要。有很棒的 explanation of that in another question. Take a look at the lambda translation document - 特别是 方法引用捕获部分 .
list.filter(String::isEmpty)
is translated as
list.filter(indy(MH(metaFactory), MH(invokeVirtual Predicate.apply), MH(invokeVirtual String.isEmpty))()))
这意味着 Predicate
在 运行 时间内不存在,除非您将其写入代码。可能有一种方便的方法可以通过反射 API 获取它,但据我所知,没有。您可以使用 dynamic proxies 编写类似的内容;但是,我认为这会给代码增加不必要的复杂性。
这是实现所需行为的 4 种不同方法的基准,包括问题中提到的方法:
Benchmark Mode Cnt Score Error Units
MyBenchmark.testNormal thrpt 30 362.782 ± 13.521 ops/us
MyBenchmark.testReflectionInvoke thrpt 30 60.440 ± 1.394 ops/us
MyBenchmark.testReflectionWrappedPredicate thrpt 30 62.948 ± 0.846 ops/us
MyBenchmark.testReflectionReturnedPredicate thrpt 30 381.780 ± 5.265 ops/us
- testNormal 通过
::
operator 访问谓词
- testReflectionInvoke直接使用
invoke()
方法 - testReflectionWrappedPredicate 将
invoke()
方法包装到Precicate
- testReflectionReturnedPredicate 使用了 returns
Predicate
的方法,所以只调用一次反射
出于此示例的目的缓存了谓词,因为我们没有测试获取谓词的速度,而是假设它只会完成一次。
从结果可以看出,反射比正常访问谓词慢6倍。因此,在我看来,cleanest/most 可维护的方法是使用不带参数的方法 return Predicate
类型 而不是 boolean
.
public static Predicate<Object> isNull() {
return obj -> obj == null;
}
已使用 3 次分叉、4 次预热迭代和 10 次迭代完成基准测试。
这是我用来测试的代码,如果你想运行自己做基准测试(使用JMH框架做基准测试):
public class MyBenchmark {
public static Predicate<Object> normal = MyBenchmark::isNull;
public static Method reflectionInvoke;
public static Predicate<Object> reflectionWrappedPredicate;
public static Predicate<Object> reflectionReturnedPredicate;
static {
try {
Method m1 = MyBenchmark.class.getMethod("isNull", Object.class);
reflectionInvoke = m1;
reflectionWrappedPredicate = a -> {
try {
return (boolean)m1.invoke(null, a);
}
catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
return false;
}
};
Method m2 = MyBenchmark.class.getMethod("isNull");
reflectionReturnedPredicate = (Predicate)m2.invoke(null);
}
catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
ex.printStackTrace();
};
}
@Benchmark
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testNormal() {
Predicate<Object> p = normal;
return p.test(this) | p.test(null);
}
@Benchmark
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testReflectionInvoke() {
try {
Method m = reflectionInvoke;
return (boolean)m.invoke(null, this) | (boolean)m.invoke(null, (Object)null);
}
catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException ex) {
ex.printStackTrace();
return false;
}
}
@Benchmark
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testReflectionWrappedPredicate() {
Predicate<Object> p = reflectionWrappedPredicate;
return p.test(this) | p.test(null);
}
@Benchmark
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public boolean testReflectionReturnedPredicate() {
Predicate<Object> p = reflectionReturnedPredicate;
return p.test(this) | p.test(null);
}
public static boolean isNull(Object obj) {
return obj == null;
}
public static Predicate<Object> isNull() {
return obj -> obj == null;
}
}