字节码测试

对比过程

编写的源程序如下,分别使用了匿名类调用、lambda、函数引用,对象内部的函数引用四种方式进行测试。

  1. package com.test.function.call;
  2. public class Test {
  3. public String name(String param) {
  4. return String.format("this::name(%s)", param);
  5. }
  6. public void call(){
  7. print(this::name);
  8. }
  9. @FunctionalInterface
  10. public interface Func<T, R> {
  11. R apply(T t);
  12. }
  13. public static void print(Func<String, String> func) {
  14. func.apply("2");
  15. }
  16. public static void main(String[] args) {
  17. Test test = new Test();
  18. System.out.println();
  19. print(new Func<String, String>() {
  20. @Override
  21. public String apply(String s) {
  22. return test.name(s);
  23. }
  24. });
  25. System.out.println();
  26. print((s) -> test.name(s));
  27. System.out.println();
  28. print(test::name);
  29. System.out.println();
  30. test.call();
  31. System.out.println();
  32. }
  33. }

先将程序编译为class文件,然后使用javap -v Test.class,查看详细字节码信息

  1. 0: new #9 // class com/test/function/call/Test
  2. 3: dup
  3. 4: invokespecial #10 // Method "<init>":()V
  4. 7: astore_1
  5. 8: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
  6. 11: invokevirtual #12 // Method java/io/PrintStream.println:()V
  7. vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  8. 14: new #13 // class com/test/function/call/Test$1
  9. 17: dup
  10. 18: aload_1
  11. 19: invokespecial #14 // Method com/test/function/call/Test$1."<init>":(Lcom/test/function/call/Test;)V
  12. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  13. 22: invokestatic #6 // Method print:(Lcom/test/function/call/Test$Func;)V
  14. 25: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
  15. 28: invokevirtual #12 // Method java/io/PrintStream.println:()V
  16. vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  17. 31: aload_1
  18. 32: invokedynamic #15, 0 // InvokeDynamic #1:apply:(Lcom/test/function/call/Test;)Lcom/test/function/call/Test$Func;
  19. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  20. 37: invokestatic #6 // Method print:(Lcom/test/function/call/Test$Func;)V
  21. 40: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
  22. 43: invokevirtual #12 // Method java/io/PrintStream.println:()V
  23. vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  24. 46: aload_1
  25. 47: dup
  26. 48: invokestatic #16 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
  27. 51: pop
  28. 52: invokedynamic #5, 0 // InvokeDynamic #0:apply:(Lcom/test/function/call/Test;)Lcom/test/function/call/Test$Func;
  29. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  30. 57: invokestatic #6 // Method print:(Lcom/test/function/call/Test$Func;)V
  31. 60: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
  32. 63: invokevirtual #12 // Method java/io/PrintStream.println:()V
  33. 66: aload_1
  34. 67: invokevirtual #17 // Method call:()V
  35. 70: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
  36. 73: invokevirtual #12 // Method java/io/PrintStream.println:()V
  37. 76: return

method的字节码

  1. vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  2. 0: aload_0
  3. 1: invokedynamic #5, 0 // InvokeDynamic #0:apply:(Lcom/test/function/call/Test;)Lcom/test/function/call/Test$Func;
  4. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  5. 6: invokestatic #6 // Method print:(Lcom/test/function/call/Test$Func;)V
  6. 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;

  1. public String name(String param) {
  2. return String.format("this::name(%s)", param);
  3. }
  4. public void call() {
  5. print(this::name);
  6. }
  7. @FunctionalInterface
  8. public interface Func<T, R> {
  9. R apply(T t);
  10. }
  11. public static void print(Func<String, String> func) {
  12. func.apply("2");
  13. }
  14. @Setup(Level.Trial)
  15. public void init() {
  16. test = new Test();
  17. }
  18. @Benchmark
  19. public void printNewFunc() {
  20. print(new Func<String, String>() {
  21. @Override
  22. public String apply(String s) {
  23. return name(s);
  24. }
  25. });
  26. }
  27. @Benchmark
  28. public void printLambda() {
  29. print((s) -> name(s));
  30. }
  31. @Benchmark
  32. public void printFunctionInterface1() {
  33. print(test::name);
  34. }
  35. @Benchmark
  36. public void printFunctionInterface2() {
  37. print(this::name);
  38. }
  39. public static void test() throws RunnerException {
  40. Options options = new OptionsBuilder().include(JmhTest.class.getName() + ".*")
  41. .warmupIterations(5).measurementIterations(5).forks(1).build();
  42. new Runner(options).run();
  43. }
  44. public static void main(String[] args) throws RunnerException {
  45. test();
  46. }

}

``` 在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略优于另外两种。