Java的几种创建实例方法的性能对比(二)
上一篇里对几种书写方式进行了简单的测试,得出了一些初步的结论。这次简单了解Lambda原理后,对测试做了一些调整,发现得到不一样的结果,而这个调整,明显更契合实际开发的场景。
暂时还没有亲自去验证,主要是从博客中了解的Lambda原理,引起一些启发,对测试代码进行了一些改善。
在此感谢这篇博客的作者:https://www.cnblogs.com/UncleWang001/p/10020611.html - lambda表达式底层处理机制
调整后的代码:
1 package com.supalle.test; 2 3 import lombok.AllArgsConstructor; 4 import lombok.Builder; 5 import lombok.Data; 6 import lombok.NoArgsConstructor; 7 8 import java.lang.reflect.Constructor; 9 import java.lang.reflect.InvocationTargetException; 10 import java.util.function.Supplier; 11 12 /** 13 * @描述:语法PK 14 * @作者:Supalle 15 * @时间:2019/7/26 16 */ 17 public class SyntaxPKTest { 18 19 20 /* 循环次数 */ 21 private final static int SIZE = 100000000; 22 23 /* 有类如下 */ 24 @Data 25 @Builder 26 @NoArgsConstructor 27 @AllArgsConstructor 28 private static class Man { 29 private String name; 30 private int age; 31 } 32 33 34 /** 35 * 使用 new Man(); 36 * 37 * @return 运行耗时 38 */ 39 public static long runWithNewConstructor() { 40 long start = System.currentTimeMillis(); 41 42 for (int i = 0; i < SIZE; i++) { 43 new SyntaxPKTest.Man(); 44 } 45 46 return System.currentTimeMillis() - start; 47 } 48 49 /** 50 * 使用反射 51 * 52 * @return 运行耗时 53 */ 54 public static long runWithReflex() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 55 Constructor<SyntaxPKTest.Man> constructor = SyntaxPKTest.Man.class.getConstructor(); 56 long start = System.currentTimeMillis(); 57 58 for (int i = 0; i < SIZE; i++) { 59 constructor.newInstance(); 60 } 61 62 return System.currentTimeMillis() - start; 63 } 64 65 /** 66 * 使用内部类调用 new Man(); 67 * 68 * @return 运行耗时 69 */ 70 public static long runWithSubClass() { 71 long start = System.currentTimeMillis(); 72 73 Supplier<Man> supplier = new Supplier<Man>() { 74 @Override 75 public Man get() { 76 return new Man(); 77 } 78 }; 79 for (int i = 0; i < SIZE; i++) { 80 supplier.get(); 81 82 } 83 84 return System.currentTimeMillis() - start; 85 } 86 87 /** 88 * 使用Lambda调用 new Man(); 89 * 90 * @return 运行耗时 91 */ 92 public static long runWithLambda() { 93 long start = System.currentTimeMillis(); 94 95 Supplier<Man> supplier = () -> new Man(); 96 97 for (int i = 0; i < SIZE; i++) { 98 supplier.get(); 99 } 100 101 return System.currentTimeMillis() - start; 102 } 103 104 105 /** 106 * 使用 MethodReference 107 * 108 * @return 运行耗时 109 */ 110 public static long runWithMethodReference() { 111 long start = System.currentTimeMillis(); 112 113 Supplier<Man> supplier = Man::new; 114 115 for (int i = 0; i < SIZE; i++) { 116 supplier.get(); 117 } 118 119 return System.currentTimeMillis() - start; 120 } 121 122 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { 123 124 // 测试前调用一下,加载Man字节码,尽量公平 125 SyntaxPKTest.Man man1 = new SyntaxPKTest.Man(); 126 SyntaxPKTest.Man man2 = new SyntaxPKTest.Man("张三", 20); 127 128 System.out.println("测试环境:CPU核心数 - " + Runtime.getRuntime().availableProcessors()); 129 130 System.out.println(); 131 132 // 这里的话对比再次调用的时间 133 System.out.println("首次使用 new Man() 耗时:" + runWithNewConstructor()); 134 System.err.println("再次使用 new Man() 耗时:" + runWithNewConstructor()); 135 System.out.println("首次使用反射 耗时:" + runWithReflex()); 136 System.err.println("再次使用反射 耗时:" + runWithReflex()); 137 System.out.println("首次使用内部类调用 new Man() 耗时:" + runWithSubClass()); 138 System.err.println("再次使用内部类调用 new Man() 耗时:" + runWithSubClass()); 139 System.out.println("首次使用Lambda调用 new Man() 耗时:" + runWithLambda()); 140 System.err.println("再次使用Lambda调用 new Man() 耗时:" + runWithLambda()); 141 System.out.println("首次使用 MethodReference 耗时:" + runWithMethodReference()); 142 System.err.println("再次使用 MethodReference 耗时:" + runWithMethodReference()); 143 144 145 } 146 147 }
这次调整,仅仅只是把内部类、Lambda、Method Reference 放到循环外边,更符合我们常用的情形。
测试结果较之上一篇真的有很大的变化。
1 首次使用 new Man() 耗时:4 2 再次使用 new Man() 耗时:1 3 首次使用反射 耗时:237 4 再次使用反射 耗时:251 5 首次使用内部类调用 new Man() 耗时:5 6 再次使用内部类调用 new Man() 耗时:2 7 首次使用Lambda调用 new Man() 耗时:41 8 再次使用Lambda调用 new Man() 耗时:3 9 首次使用 MethodReference 耗时:4 10 再次使用 MethodReference 耗时:1
1 首次使用 new Man() 耗时:3 2 再次使用 new Man() 耗时:2 3 首次使用反射 耗时:240 4 再次使用反射 耗时:256 5 首次使用内部类调用 new Man() 耗时:5 6 再次使用内部类调用 new Man() 耗时:3 7 首次使用Lambda调用 new Man() 耗时:43 8 再次使用Lambda调用 new Man() 耗时:4 9 首次使用 MethodReference 耗时:4 10 再次使用 MethodReference 耗时:1
首次使用 new Man() 耗时:3 再次使用 new Man() 耗时:2 首次使用反射 耗时:238 再次使用反射 耗时:251 首次使用内部类调用 new Man() 耗时:5 再次使用内部类调用 new Man() 耗时:1 首次使用Lambda调用 new Man() 耗时:39 再次使用Lambda调用 new Man() 耗时:5 首次使用 MethodReference 耗时:3 再次使用 MethodReference 耗时:2
可以看到Lambda和Method Reference同样耗时非常小,主要是在使用其创建了Supplier<Man>接口的实现实例后,本质上就和内部类创建出来的对象没有差别,因此在性能表现上相差不大。
结论:
如果既要保持灵活简洁又追求极致的性能时,那么使用在Lambda或者Method Reference时,尽量不要写在循环内部。