Java 使用存储在字符串中的名称调用方法,而不使用反射API?
我知道,使用反射API,我们可以通过存储在字符串中的方法名来调用它们 但是,反射API不能用于高性能应用程序。 在我的应用程序中,将以非常高的速率调用方法。因此,我不能使用反射API 那么,反射API的替代方案是什么 我做了研究,发现可以使用cglib和其他代码生成库 但是,我并没有发现任何示例可以通过存储在字符串中的方法名来调用该方法 使用反射选项也可以作为一个很好的例子 更新:Java 使用存储在字符串中的名称调用方法,而不使用反射API?,java,reflection,cglib,Java,Reflection,Cglib,我知道,使用反射API,我们可以通过存储在字符串中的方法名来调用它们 但是,反射API不能用于高性能应用程序。 在我的应用程序中,将以非常高的速率调用方法。因此,我不能使用反射API 那么,反射API的替代方案是什么 我做了研究,发现可以使用cglib和其他代码生成库 但是,我并没有发现任何示例可以通过存储在字符串中的方法名来调用该方法 使用反射选项也可以作为一个很好的例子 更新: 实际上,我正在实现一些主从通信API。在其中,从机将远程调用主方法。而且,方法调用的速率将非常高(大约每秒50次方
实际上,我正在实现一些主从通信API。在其中,从机将远程调用主方法。而且,方法调用的速率将非常高(大约每秒50次方法调用)。因为,主服务器不断地轮询从服务器以获得任何响应。那么,我应该以如此高的调用率尝试反射吗?这就是反射的用途。在排除这种可能性之前,我建议您尝试一下,看看在过去几年的任何JVM上,您是否确实看到了与之相关的任何性能问题。我想你不会的 您唯一的其他实际选择(实际上,有;)是一个方法,您可以让人们调用,传入要调用的方法的名称,然后分派到该方法(例如,使用一个大的
开关,或分派表,或类似工具)。例如:
Cglib附带了一个名为FastMethod
的类。此类的目的是通过使用非反射接口调用给定的方法。为此,FastMethod
实现接口并生成用于调用指定方法的字节码,从而避免了所谓昂贵的反射调用
但是,这里有两个原因说明为什么您不应该使用这个类。Cglib是很久以前写的。在这些日子里,反思比今天更昂贵。然而,现代的JVM。默认情况下,JVM将在第15次反射调用后生成用于调用方法的字节码。这正是cglib提供给您的显式操作
此外,最昂贵的不是反射调用,而是查找。您仍然需要命名要表示为FastMethod
的方法。因此,即使使用cglib,也无法避免这些成本
因此,我建议您依赖反射,直到您真正确定这是性能瓶颈。至少,使用类似的工具来证明这种实现的合理性。此外,认为类消耗了可以引起的Prm Ge/Meta空间。 < P>这是一种常见的误解,即反射是缓慢的。那是在Java1.3左右的时代
但在现代Java中,反射确实得到了很好的优化。它在引擎盖下使用动态字节码生成。此外,JVM可以将此类调用直接内联到调用方,因此使用反射调用方法的速度几乎与直接调用一样快
这里的其他评论建议使用cglib的FastMethod。事实上,它并不比反射快多少。下面是一个使用著名框架编写的基准测试:
@State(Scope.Benchmark)
public class MethodInvoke {
Method method;
FastMethod fastMethod;
@Setup
public void init() throws NoSuchMethodException {
method = getClass().getMethod("foo", int.class, long.class);
method.setAccessible(true);
fastMethod = FastClass.create(getClass()).getMethod("foo", new Class[] { int.class, long.class });
}
@GenerateMicroBenchmark
public long fastMethod() throws Exception {
return (Long) fastMethod.invoke(this, new Object[] {2, 3L});
}
@GenerateMicroBenchmark
public long reflection() throws Exception {
return (Long) method.invoke(this, 2, 3L);
}
public long foo(int a, long b) {
return a + b;
}
}
Java 7u51(64位)上的结果:
你看,FastMethod.invoke
只比Method.invoke
快3%,但与反射相反,FastMethod通常不执行正确的参数验证等,方法的内省调用分为两个阶段:首先需要定位要调用的目标方法,然后在目标实例上调用该方法,并为其提供参数
在此过程中,最昂贵的操作是在类中定位目标方法(例如,method-method=TargetClass.getMethod(class[]signature…)
一旦获得方法对象,就调用对象上的方法,如:method.invoke(targetObj,param…)
是一种轻量级操作,其成本略高于直接方法调用
为了演示这一点,我刚刚对这三种方法进行了一次快速而不精确的比较,结果如下(您需要将它们相互比较):
- 每次反射方法+调用:167ms
- 类+调用的第一个缓存方法:36毫秒
- 直接调用:17ms
请注意,内省具有固定的性能成本,因此该方法进行的计算越多,这些数字就越接近
我在以前的项目中使用过这种方法缓存方法,在这些项目中,性能非常重要。在实践中,您观察到实际的方法执行时间使得内省的成本可以忽略。(cfr。)
测试代码(承受快速和肮脏)
import java.lang.reflect.Method;
导入java.util.Random;
/**
*maasg于2014年5月10日创建。
*/
公共类仪器{
公共静态void main(字符串[]参数)引发异常{
Random ran=新的Random();
字符串[]方法=新字符串[]{“method1”、“method2”、“method3”};
目标=新目标();
//热身
对于(inti=0;i,自Java 1.7以来,有一种新的方法可以通过MethodHandle
从名称调用方法,并且比反射快得多
我在一台蹩脚的笔记本电脑上做了一些基准测试:
反射每秒可以进行大约400万次调用
MethodHandle每秒可以执行大约1.4亿次调用
这两种方法都是可以接受的性能,但平均调用时间只有7纳秒,使用MethodHandle非常好
如果您在HashMap中根据名称存储了对MethodHandle的引用,则可以在后续调用中重用。解析和处理字符串比使用反射要昂贵得多。反射确实需要花费,但不如查找字符串那么多。您对性能的要求是什么?您的问题在于choos的分类方法
@State(Scope.Benchmark)
public class MethodInvoke {
Method method;
FastMethod fastMethod;
@Setup
public void init() throws NoSuchMethodException {
method = getClass().getMethod("foo", int.class, long.class);
method.setAccessible(true);
fastMethod = FastClass.create(getClass()).getMethod("foo", new Class[] { int.class, long.class });
}
@GenerateMicroBenchmark
public long fastMethod() throws Exception {
return (Long) fastMethod.invoke(this, new Object[] {2, 3L});
}
@GenerateMicroBenchmark
public long reflection() throws Exception {
return (Long) method.invoke(this, 2, 3L);
}
public long foo(int a, long b) {
return a + b;
}
}
Benchmark Mode Samples Mean Mean error Units
b.MethodInvoke.fastMethod thrpt 5 79248,583 3978,941 ops/ms
b.MethodInvoke.reflection thrpt 5 76975,414 2844,730 ops/ms
import java.lang.reflect.Method;
import java.util.Random;
/**
* Created by maasg on 5/10/14.
*/
public class Instrospection {
public static void main(String [] params) throws Exception {
Random ran = new Random();
String[] methods = new String[] {"method1", "method2", "method3"};
Target target = new Target();
// Warmup
for (int i=0; i<1000; i++) {
String methodName = methods[ran.nextInt(3)];
String param = new Integer(ran.nextInt()).toString();
Method method = Target.class.getMethod(methodName, String.class);
}
StringBuilder builder = new StringBuilder();
long t0 = System.currentTimeMillis();
for (int i=0; i<100000; i++) {
String methodName = methods[ran.nextInt(3)];
String param = new Integer(ran.nextInt()).toString();
Method method = Target.class.getMethod(methodName, String.class);
Object result = method.invoke(target, "param");
builder.append(result.toString());
}
System.out.println("Elapsed 1: "+(System.currentTimeMillis()-t0));
Method[] invokeMethods = new Method[] {
Target.class.getMethod(methods[0], String.class),
Target.class.getMethod(methods[1], String.class),
Target.class.getMethod(methods[2], String.class),
};
builder = new StringBuilder();
long t1 = System.currentTimeMillis();
for (int i=0; i<100000; i++) {
String param = new Integer(ran.nextInt()).toString();
Method method = invokeMethods[ran.nextInt(3)];
Object result = method.invoke(target, "param");
builder.append(result.toString());
}
System.out.println("Elapsed 2: "+(System.currentTimeMillis()-t1));
builder = new StringBuilder();
long t2 = System.currentTimeMillis();
for (int i=0; i<100000; i++) {
Object result = null;
String param = new Integer(ran.nextInt()).toString();
switch (ran.nextInt(3)) {
case 0: result = target.method1(param);
case 1: result = target.method2(param);
case 2: result = target.method3(param);
}
builder.append(result.toString());
}
System.out.println("Elapsed 3: "+(System.currentTimeMillis()-t2));
}
}