字节码测试
对比过程
编写的源程序如下,分别使用了匿名类调用、lambda、函数引用,对象内部的函数引用四种方式进行测试。
package com.test.function.call;public class Test {public String name(String param) {return String.format("this::name(%s)", param);}public void call(){print(this::name);}@FunctionalInterfacepublic interface Func<T, R> {R apply(T t);}public static void print(Func<String, String> func) {func.apply("2");}public static void main(String[] args) {Test test = new Test();System.out.println();print(new Func<String, String>() {@Overridepublic String apply(String s) {return test.name(s);}});System.out.println();print((s) -> test.name(s));System.out.println();print(test::name);System.out.println();test.call();System.out.println();}}
先将程序编译为class文件,然后使用javap -v Test.class,查看详细字节码信息
0: new #9 // class com/test/function/call/Test3: dup4: invokespecial #10 // Method "<init>":()V7: astore_18: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;11: invokevirtual #12 // Method java/io/PrintStream.println:()Vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv14: new #13 // class com/test/function/call/Test$117: dup18: aload_119: invokespecial #14 // Method com/test/function/call/Test$1."<init>":(Lcom/test/function/call/Test;)V^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^22: invokestatic #6 // Method print:(Lcom/test/function/call/Test$Func;)V25: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;28: invokevirtual #12 // Method java/io/PrintStream.println:()Vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv31: aload_132: invokedynamic #15, 0 // InvokeDynamic #1:apply:(Lcom/test/function/call/Test;)Lcom/test/function/call/Test$Func;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^37: invokestatic #6 // Method print:(Lcom/test/function/call/Test$Func;)V40: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;43: invokevirtual #12 // Method java/io/PrintStream.println:()Vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv46: aload_147: dup48: invokestatic #16 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;51: pop52: invokedynamic #5, 0 // InvokeDynamic #0:apply:(Lcom/test/function/call/Test;)Lcom/test/function/call/Test$Func;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^57: invokestatic #6 // Method print:(Lcom/test/function/call/Test$Func;)V60: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;63: invokevirtual #12 // Method java/io/PrintStream.println:()V66: aload_167: invokevirtual #17 // Method call:()V70: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;73: invokevirtual #12 // Method java/io/PrintStream.println:()V76: return
method的字节码
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv0: aload_01: invokedynamic #5, 0 // InvokeDynamic #0:apply:(Lcom/test/function/call/Test;)Lcom/test/function/call/Test$Func;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^6: invokestatic #6 // Method print:(Lcom/test/function/call/Test$Func;)V9: return
可以统计到以下结果:
- 匿名类的方式使用了4个字节码,且使用了new指令
- lambda的方式使用两个字节码
- 函数引用的方式使用5个字节码
- 对象内的函数引用方式使用两个字节码
结论
除了匿名类的方式外,另外三种方式都非常高效。特殊情况是:在对象外使用时,尽量考虑lambda的方式,性能略高于函数引用。Benchmark测试
对比过程
编写的源程序如下,借助了Jmh工具,分别使用了匿名类调用、lambda、函数引用,对象内部的函数引用四种方式进行测试。 ``` import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Thread) public class JmhTest { private Test test;
public String name(String param) {return String.format("this::name(%s)", param);}public void call() {print(this::name);}@FunctionalInterfacepublic interface Func<T, R> {R apply(T t);}public static void print(Func<String, String> func) {func.apply("2");}@Setup(Level.Trial)public void init() {test = new Test();}@Benchmarkpublic void printNewFunc() {print(new Func<String, String>() {@Overridepublic String apply(String s) {return name(s);}});}@Benchmarkpublic void printLambda() {print((s) -> name(s));}@Benchmarkpublic void printFunctionInterface1() {print(test::name);}@Benchmarkpublic void printFunctionInterface2() {print(this::name);}public static void test() throws RunnerException {Options options = new OptionsBuilder().include(JmhTest.class.getName() + ".*").warmupIterations(5).measurementIterations(5).forks(1).build();new Runner(options).run();}public static void main(String[] args) throws RunnerException {test();}
}
``` 在Idea中Run运行,结果如下:
| 名称 | Benchmark | Mode | Cnt | Score | Units |
|---|---|---|---|---|---|
| 函数引用 | printFunctionInterface1 | avgt | 5 | 307.805 | ns/op |
| 对象内部的函数引用 | printFunctionInterface2 | avgt | 5 | 309.530 | ns/op |
| lambda表达式 | printLambda | avgt | 5 | 307.619 | ns/op |
| 匿名类 | printNewFunc | avgt | 5 | 317.056 | ns/op |
结论
匿名类由于存在new指令,所以执行时间高于其他方式,而另外三种性能差别不大,但是lambda略优于另外两种。
