字节码测试
对比过程
编写的源程序如下,分别使用了匿名类调用、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);
}
@FunctionalInterface
public 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>() {
@Override
public 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/Test
3: dup
4: invokespecial #10 // Method "<init>":()V
7: astore_1
8: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
11: invokevirtual #12 // Method java/io/PrintStream.println:()V
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
14: new #13 // class com/test/function/call/Test$1
17: dup
18: aload_1
19: 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;)V
25: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
28: invokevirtual #12 // Method java/io/PrintStream.println:()V
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
31: aload_1
32: 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;)V
40: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
43: invokevirtual #12 // Method java/io/PrintStream.println:()V
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
46: aload_1
47: dup
48: invokestatic #16 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
51: pop
52: 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;)V
60: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
63: invokevirtual #12 // Method java/io/PrintStream.println:()V
66: aload_1
67: invokevirtual #17 // Method call:()V
70: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
73: invokevirtual #12 // Method java/io/PrintStream.println:()V
76: return
method的字节码
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
0: aload_0
1: 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;)V
9: 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);
}
@FunctionalInterface
public 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();
}
@Benchmark
public void printNewFunc() {
print(new Func<String, String>() {
@Override
public String apply(String s) {
return name(s);
}
});
}
@Benchmark
public void printLambda() {
print((s) -> name(s));
}
@Benchmark
public void printFunctionInterface1() {
print(test::name);
}
@Benchmark
public 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略优于另外两种。