Javareflection的更快捷的select

正如我们所知,reflection是一种灵活而缓慢的方法,可以在运行时维护和修改代码的行为。

但是,如果我们必须使用这样的function,那么与reflectionAPI相比,Java中是否有更快的编程技术用于dynamic修改? 这些替代品的反面反思有什么优点和缺点?

Reflection的一个替代方法是dynamic生成类文件。 这个生成的类应该执行所需的操作,例如调用在运行时发现的方法,并在编译时实现已知的interface ,以便可以使用该接口以非reflection方式调用生成的方法。 有一个问题:如果可行的话,Reflection在内部也是一样的。 这在特殊情况下不起作用,例如当调用private方法时,您无法生成调用它的合法类文件。 所以在Reflection实现中,有不同types的调用处理程序,使用生成的代码或本地代码。 你不能打败。

但更重要的是,Reflection会对每个调用进行安全检查。 所以你的生成的类将被加载和实例检查,只有这可以是一个巨大的胜利。 但是也可以在Method实例上调用setAccessible(true)来打开安全检查。 那么只有autoboxing和varargs数组创build的小小的性能损失仍然存在。

Java 7开始MethodHandle有两种MethodHandle 。 与其他两个不同,它的优势在于它甚至可以在安全限制的环境中工作。 MethodHandle的访问检查是在获取它时执行的,而不是在调用它时执行的。 它有所谓的“多态签名”,这意味着你可以使用任意参数types来调用它,而不需要自动装箱或创build数组。 当然,错误的参数types将会创build一个合适的RuntimeException

更新 )在Java 8中 ,可以select在运行时使用lambdaexpression式和方法引用语言function的后端。 这个后台完成了刚开始描述的事情,dynamic生成一个类,它实现了一个interface当代码在编译时被知道的时候,它可以直接调用它。 确切的机制是特定于实现的,因此未定义,但是您可以假设实现将尝试尽可能快地进行调用。 目前Oracle的JRE实现完美。 不仅如此,您可以免除生成这样的访问器类的负担,还可以执行您永远无法做到的事情 – 甚至可以通过生成的代码调用private方法。 我已经更新了这个例子来包含这个解决scheme。 这个例子使用了一个已经存在的标准interface ,并且碰巧有所需的方法签名。 如果不存在这样的匹配interface ,则必须使用具有正确签名的方法创build您自己的访问器function接口。 但是,当然,现在示例代码需要运行Java 8。

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

 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; } } 

在我的Java 7设置打印的旧程序: direct: 0,03s, mh: 0,32s, reflection: 1,05s ,这表明MethodHandle是一个很好的select。 现在,在同一台机器上运行在Java 8下的更新程序direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40s打印direct: 0,02s, lambda: 0,02s, mh: 0,35s, reflection: 0,40s ,这清楚地表明reflection性能已经提高到一定程度可能会使MethodHandle处理MethodHandle不必要,除非你用它来做lambda技巧,这显然胜过了所有的reflectionselect,这并不奇怪,因为它只是一个直接的调用(好,几乎:一级间接)。 请注意,我使目标方法是private以展示有效地调用private方法的能力。

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

我创build了一个名为lambda-factory的小型库。 它基于LambdaMetafactory,但为您节省寻找或创build与方法相匹配的界面的麻烦。

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

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

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

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

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

 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! 

请注意,在调用lambda时,必须select一个包含目标方法返回types的调用方法。 – 可变参数和汽车拳击太昂贵了。

在上面的例子中,所选的invoke_for_float方法表明我们正在调用一个返回一个float的方法。 如果你试图访问的方法返回一个string,一个盒装原语(整数,布尔等)或一些自定义对象,你可以调用invoke_for_Object

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

  1. 静态调用和实例调用
  2. 访问私有方法和来自其他包的方法
  3. 'invokeSpecial'逻辑,即创build的实现是这样的,它绕过dynamic方法调度。

reflection的替代是使用接口。 刚刚从Joshua Bloch的Effective Java拿走。

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

使用reflection仅用于创build对象即

 // 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); } 

虽然这个程序只是一个玩具,但它演示的技术非常强大。 玩具程序可以很容易地变成一个通用集合testing器,通过积极操纵一个或多个实例并检查他们是否遵守Set合同来validation指定的Set实现。 同样,它可以变成一个通用集性能分析工具。 事实上,这项技术足够强大,可以实现一个全面的服务提供商框架。 大多数时候,这种技术就是你所需要的所有反思方式。

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