Java 反射的更快替代方案

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19557829/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 18:19:09  来源:igfitidea点击:

Faster alternatives to Java's reflection

javaperformancereflection

提问by ovunccetin

As we know, reflection is a flexible but slowmethod to maintain and modify the behaviour of the code at runtime.

众所周知,反射是一种灵活但缓慢的方法,用于在运行时维护和修改代码的行为。

But if we have to use such a functionality, are there any faster programming techniques in Java compared to Reflection API for dynamic modifications? What are pros and cons of these alternatives against reflection?

但是,如果我们必须使用这样的功能,与动态修改的反射 API 相比,Java 中有没有更快的编程技术?这些针对反射的替代方案的优缺点是什么?

采纳答案by Holger

One alternative to Reflection is to generate a class file dynamically. This generated class ought to perform the desired action, e.g. invokes the method discovered at runtime, and implements an interfaceknown at compile-time so that it's possible to invoke the generated method in a non-reflective way using that interface. There's one catch: if applicable, Reflection does the same trick internally. This does not work in special cases, e.g. when invoking a privatemethod as you can't generate a legal class file invoking it. So in the Reflection implementation there are different types of invocation handlers, using either generated code or native code. You can't beat that.

反射的一种替代方法是动态生成类文件。这个生成的类应该执行所需的操作,例如调用在运行时发现的方法,并interface在编译时实现一个已知的,以便可以使用该接口以非反射方式调用生成的方法。有一个问题:如果适用,反射在内部执行相同的技巧。这在特殊情况下不起作用,例如在调用private方法时,因为您无法生成调用它的合法类文件。所以在反射实现中有不同类型的调用处理程序,使用生成的代码或本机代码。你不能打败它。

But more important is that Reflection does security checks on every invocation. So your generated class will be checked on loading and instantiation only which can be a big win. Alternatively you can invoke setAccessible(true)on a Methodinstance to turn the security checks off. Then only the minor performance loss of autoboxing and varargs array creation remains.

但更重要的是,反射会对每次调用进行安全检查。因此,您生成的类将仅在加载和实例化时进行检查,这可能是一个巨大的胜利。或者,您可以setAccessible(true)Method实例上调用以关闭安全检查。然后只有自动装箱和可变参数数组创建的轻微性能损失仍然存在。

Since Java?7there is an alternative to both, the MethodHandle. The big advantage is that, unlike the other two, it even works in security restricted environments. The access checks for a MethodHandleare performed when acquiring it but not when invoking it. It has the so-called “polymorphic signature” which means you can invoke it with arbitrary argument types without auto-boxing nor array creation. Of course, wrong argument types will create an appropriate RuntimeException.

Java?7以来,两者都有一个替代方案,MethodHandle. 最大的优点是,与其他两个不同,它甚至可以在安全受限的环境中工作。对 a 的访问检查MethodHandle是在获取它时执行的,而不是在调用它时执行。它具有所谓的“多态签名”,这意味着您可以使用任意参数类型调用它,而无需自动装箱或创建数组。当然,错误的参数类型会创建一个合适的RuntimeException.

(Update) With Java?8, there is the option to use the back-end of the lambda expression and method reference language feature at runtime. This backend does exactly the thing described at the beginning, generating a class dynamically which implements an interfaceyour code may call directly when it is known at compile-time. The exact mechanics is implementation-specific, hence undefined, but you can assume that the implementation will try it's best to make the invocation as fast as possible. The current implementation of Oracle's JRE does it perfectly. Not only that this saves you from the burden of generating such an accessor class, it is also capable of doing what you never could do— invoke even privatemethods via generated code. I have updated the example to include this solution. This example uses a standard interfacewhich already exists and happens to have the desired method signature. If no such matching interfaceexists, you have to create your own accessor functional interface with a method with the right signature. But, of course, now the example code requires Java?8 to run.

更新)在Java?8 中,可以选择在运行时使用 lambda 表达式和方法引用语言功能的后端。这个后端完全按照开头描述的那样做,动态生成一个类,该类实现interface您的代码可以在编译时已知时直接调用。确切的机制是特定于实现的,因此未定义,但您可以假设实现将尽最大努力使调用尽可能快。Oracle 的 JRE 的当前实现完美地做到了这一点。这不仅使您免于生成此类访问器类的负担,而且还能够做您永远做不到的事情——甚至调用private方法通过生成的代码。我已更新示例以包含此解决方案。这个例子使用了一个interface已经存在并且恰好具有所需方法签名的标准。如果不interface存在这样的匹配,则必须使用具有正确签名的方法创建自己的访问器功能接口。但是,当然,现在示例代码需要 Java?8 才能运行。

Here is a simple benchmark example:

这是一个简单的基准测试示例:

import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.function.IntBinaryOperator;

public class TestMethodPerf
{
  private static final int ITERATIONS = 50_000_000;
  private static final int WARM_UP = 10;

  public static void main(String... args) throws Throwable
  {
 // hold result to prevent too much optimizations
    final int[] dummy=new int[4];

    Method reflected=TestMethodPerf.class
      .getDeclaredMethod("myMethod", int.class, int.class);
    final MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle mh=lookup.unreflect(reflected);
    IntBinaryOperator lambda=(IntBinaryOperator)LambdaMetafactory.metafactory(
      lookup, "applyAsInt", MethodType.methodType(IntBinaryOperator.class),
      mh.type(), mh, mh.type()).getTarget().invokeExact();

    for(int i=0; i<WARM_UP; i++)
    {
      dummy[0]+=testDirect(dummy[0]);
      dummy[1]+=testLambda(dummy[1], lambda);
      dummy[2]+=testMH(dummy[1], mh);
      dummy[3]+=testReflection(dummy[2], reflected);
    }
    long t0=System.nanoTime();
    dummy[0]+=testDirect(dummy[0]);
    long t1=System.nanoTime();
    dummy[1]+=testLambda(dummy[1], lambda);
    long t2=System.nanoTime();
    dummy[2]+=testMH(dummy[1], mh);
    long t3=System.nanoTime();
    dummy[3]+=testReflection(dummy[2], reflected);
    long t4=System.nanoTime();
    System.out.printf("direct: %.2fs, lambda: %.2fs, mh: %.2fs, reflection: %.2fs%n",
      (t1-t0)*1e-9, (t2-t1)*1e-9, (t3-t2)*1e-9, (t4-t3)*1e-9);

    // do something with the results
    if(dummy[0]!=dummy[1] || dummy[0]!=dummy[2] || dummy[0]!=dummy[3])
      throw new AssertionError();
  }

  private static int testMH(int v, MethodHandle mh) throws Throwable
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=(int)mh.invokeExact(1000, v);
    return v;
  }

  private static int testReflection(int v, Method mh) throws Throwable
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=(int)mh.invoke(null, 1000, v);
    return v;
  }

  private static int testDirect(int v)
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=myMethod(1000, v);
    return v;
  }

  private static int testLambda(int v, IntBinaryOperator accessor)
  {
    for(int i=0; i<ITERATIONS; i++)
      v+=accessor.applyAsInt(1000, v);
    return v;
  }

  private static int myMethod(int a, int b)
  {
    return a<b? a: b;
  }
}

Th old program printed in my Java?7 setup: direct: 0,03s, mh: 0,32s, reflection: 1,05swhich suggested that MethodHandlewas a good alternative. Now, the updated program running under Java?8 on the same machine printed direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40swhich clearly shows that Reflection performance has been improved to a degree that might make dealing with MethodHandleunnecessary, unless you use it to do the lambda trick, that clearly outperforms all reflective alternatives, which comes at no surprise, as it is just a direct call (well, almost: one level of indirection). Note that I made the target method privateto demonstrate the capability of calling even privatemethods efficiently.

在我的 Java?7 设置中打印的旧程序:direct: 0,03s, mh: 0,32s, reflection: 1,05s这表明这MethodHandle是一个不错的选择。现在,在同一台机器上运行在 Java?8 下的更新程序direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40s清楚地表明反射性能已经提高到可能使处理MethodHandle不必要的程度,除非您使用它来执行 lambda 技巧,这显然优于所有反射替代方案,这并不奇怪,因为它只是一个直接调用(好吧,几乎:一级间接)。请注意,我制作目标方法是private为了演示private有效调用偶数方法的能力。

As always, I have to point at the simplicity of this benchmark and how artificial it is. But I think, the tendency is clearly visible and even more important, the results are convincingly explainable.

与往常一样,我必须指出这个基准测试的简单性以及它是多么人为。但我认为,这种趋势是显而易见的,更重要的是,结果是可以令人信服地解释的。

回答by Trying

The alternate for reflection is using Interface. Just taking from Effective Java by Joshua Bloch.

反射的替代方法是使用接口。刚刚从Joshua Bloch 的 Effective Java 中获取。

We can obtain many of the benefits of reflection while incurring few of its costs by using it only in a very limited form. For many programs that must use a class that is unavailable at compile time, there exists at compile time an appropriate interface or superclass by which to refer to the class. If this is the case, you can create instances reflectively and access them normally via their interface or superclass. If the appropriate constructor has no parameters, then you don't even need to use java.lang.reflect; the Class.newInstance method provides the required functionality.

通过仅以非常有限的形式使用反射,我们可以获得反射的许多好处,同时只产生很少的成本。对于许多必须使用在编译时不可用的类的程序,在编译时存在一个适当的接口或超类来引用该类。如果是这种情况,您可以反射性地创建实例并通过它们的接口或超类正常访问它们。如果合适的构造函数没有参数,那么你甚至不需要使用 java.lang.reflect;Class.newInstance 方法提供了所需的功能。

Use reflection for only for creating the object i.e.

使用反射仅用于创建对象,即

// Reflective instantiation with interface access
   public static void main(String[] args) {
       // Translate the class name into a Class object
       Class<?> cl = null;
       try {
           cl = Class.forName(args[0]);
       } catch(ClassNotFoundException e) {
           System.err.println("Class not found.");
           System.exit(1);
       }
       // Instantiate the class
       Set<String> s = null;
       try {
           s = (Set<String>) cl.newInstance();
       } catch(IllegalAccessException e) {
           System.err.println("Class not accessible.");
           System.exit(1);
       } catch(InstantiationException e) {
           System.err.println("Class not instantiable.");
           System.exit(1);
       }
       // Exercise the set
       s.addAll(Arrays.asList(args).subList(1, args.length));
       System.out.println(s);
}

While this program is just a toy, the technique it demonstrates is very powerful. The toy program could easily be turned into a generic set tester that validates the specified Set implementation by aggressively manipulating one or more instances and checking that they obey the Set contract. Similarly, it could be turned into a generic set performance analysis tool. In fact, the technique is sufficiently powerful to implement a full-blown service provider framework . Most of the time, this technique is all that you need in the way of reflection.

This example demonstrates two disadvantages of reflection. First, the example can generate three runtime errors, all of which would have been compile-time errors if reflective instantiation were not used. Second, it takes twenty lines of tedious code to generate an instance of the class from its name, whereas a con- structor invocation would fit neatly on a single line. These disadvantages are, however, restricted to the part of the program that instantiates the object. Once instantiated, it is indistinguishable from any other Set instance.

虽然这个程序只是一个玩具,但它展示的技术非常强大。这个玩具程序可以很容易地变成一个通用的集合测试器,它通过积极地操纵一个或多个实例并检查它们是否遵守 Set 契约来验证指定的 Set 实现。同样,它可以变成一个通用的集合性能分析工具。事实上,该技术足以实现一个成熟的服务提供者框架。大多数时候,这种技术就是你在反射方式中所需要的。

这个例子展示了反射的两个缺点。首先,该示例可能会生成三个运行时错误,如果不使用反射实例化,所有这些都会是编译时错误。其次,从类的名称生成类的实例需要 20 行乏味的代码,而构造函数调用则可以整齐地放在一行中。然而,这些缺点仅限于实例化对象的程序部分。一旦实例化,它就与任何其他 Set 实例无法区分。

回答by Hervian

I have created a small library called lambda-factory. It is based on LambdaMetafactory, but saves you the hassle of finding or creating an interface that matches the method.

我创建了一个名为lambda-factory的小型库。它基于 LambdaMetafactory,但为您省去了查找或创建与方法匹配的接口的麻烦。

Here are some sample runtimes for 10E8 iterations (reproducable with the class PerformanceTest):

以下是 10E8 迭代的一些示例运行时(可使用类 PerformanceTest 重现):

Lambda: 0.02s, Direct: 0.01s, Reflection: 4.64s for method(int, int)
Lambda: 0.03s, Direct: 0.02s, Reflection: 3.23s for method(Object, int)

Lambda: 0.02s, Direct: 0.01s, Reflection: 4.64s for method(int, int)
Lambda: 0.03s, Direct: 0.02s, Reflection: 3.23s for method(Object, int)

Let's say we have a class called MyClass, which defines the following methods:

假设我们有一个名为 的类MyClass,它定义了以下方法:

private static String myStaticMethod(int a, Integer b){ /*some logic*/ }
private float myInstanceMethod(String a, Boolean b){ /*some logic*/ }

We can access these methods like this:

我们可以像这样访问这些方法:

Method method = MyClass.class.getDeclaredMethod("myStaticMethod", int.class, Integer.class); //Regular reflection call
Lambda lambda = LambdaFactory.create(method);  
String result = (String) lambda.invoke_for_Object(1000, (Integer) 565); //Don't rely on auto boxing of arguments!

Method method = MyClass.class.getDeclaredMethod("myInstanceMethod", String.class, Boolean.class);
Lambda lambda = LambdaFactory.create(method);
float result = lambda.invoke_for_float(new MyClass(), "Hello", (Boolean) null);  //No need to cast primitive results!

Notice that when invoking the lambda, you must choose an invocation method that contains the target method's return type in its name. - varargs and auto boxing were too expensive.

请注意,在调用 lambda 时,您必须选择一个在其名称中包含目标方法的返回类型的调用方法。- 可变参数和自动拳击太贵了。

In the example above, the chosen invoke_for_floatmethod indicates that we are invoking a method, which returns a float. If the method you are trying to access returns fx a String, a boxed primitive (Integer, Boolean etc) or some custom Object, you would call invoke_for_Object.

在上面的例子中,选择的invoke_for_float方法表明我们正在调用一个方法,它返回一个浮点数。如果您尝试访问的方法返回 fx 字符串、装箱原语(整数、布尔值等)或某些自定义对象,您将调用invoke_for_Object.

The project is a good template for experimenting with LambdaMetafactory since it contains working code for various aspects:

该项目是用于试验 LambdaMetafactory 的好模板,因为它包含各个方面的工作代码:

  1. static calls and instance calls
  2. Access to private methods, and methods from other packages
  3. 'invokeSpecial' logic, i.e. where the created implementation is such, that it bypasses dynamic method dispatch.
  1. 静态调用和实例调用
  2. 访问私有方法和来自其他包的方法
  3. 'invokeSpecial' 逻辑,即所创建的实现绕过动态方法分派。