测试反射实现的对象生成、方法调用的性能。在这里被测试的类代码如下所示。
public class TestBean {
public final static String DEFAULT="default";
private String word;
public TestBean() {
word=DEFAULT;
}
public String getWord(){
return word;
}
public void setWord(String word){
this.word=word;
}
}
这个类很简单,提供一个构造函数,一个get和set属性值的方法。再比较直接调用的性能,普通反射的性能,带缓存的反射性能。
首先建立了一个测试框架,对给定的测试类统计用时。代码如下所示。
package reflectTest;
/**简易测试框架,统计运行时间
* */
public class TestBeanCount{
public static void testBean(TestBeanRun tbr){
System.out.printf("%10s\t%5s\t%6s\n","method","time","avr" );
long[]time=new long[3];
String[] methods = new String[] { "construct", "get", "set" };
for (int t = 10; t-- > 0;) {
tbr.setTestMethod(TestBeanRun.CONSTRUCT);
time[0] = getUsedTime(tbr);
tbr.setTestMethod(TestBeanRun.GET);
time[1] = getUsedTime(tbr);
tbr.setTestMethod(TestBeanRun.SET);
time[2] = getUsedTime(tbr);
for (int k = 0; k < 3; ++k) {
System.out.printf("%10s\t%5d\t%6.3f\n", methods[k], time[k],
time[k] * 0.001);
}
}
}
public static final long DEFAULT_RUN_TIME=100000000L;
public static long getUsedTime(Runnable run,long runTime){
long start=System.currentTimeMillis();
while(runTime-->0){
run.run();
}
return System.currentTimeMillis()-start;
}
public static long getUsedTime(Runnable run){
return getUsedTime(run,DEFAULT_RUN_TIME);
}
}
这里用到了一个类TestBeanRun,这个类实现了接口Runnable,用以执行方法调用,如对象生成,get/set方法调用。测试框架对这个类的三个类型的方法调用分别执行10^8次,然后统计用时,计算平均时间。
TestBeanRun的代码如下所示。这个类里面用一个标志位来选择执行哪种调用,生成对象,或者get调用,set调用。这里默认的是进行常规的直接调用。普通反射和带缓存反射的测试通过继承这个类,并重写那三个方法。
package reflectTest;
public class TestBeanRun implements Runnable{
public static final int CONSTRUCT=1;
public static final int GET=2;
public static final int SET=3;
private int testMethod;
protected String s;
protected TestBean tb;
private final static TestBean TB = new TestBean();
public void setTestMethod(int testMethod){
this.testMethod=testMethod;
}
@Override
public void run() {
switch(this.testMethod){
case CONSTRUCT:
testConstruct();
break;
case GET:
testGet();
break;
case SET:
testSet();
break;
default:
}
}
public void testConstruct(){
tb=new TestBean();
}
public void testGet(){
s=TB.getWord();
}
public void testSet(){
TB.setWord("ANOTHER");
}
}
测试代码如下所示。
package reflectTest;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**测试正常调用和反射调用下的执行效率问题。
* 测试范围:构造函数,get,set方法;
* 测试类:TestBean;
* 测试过程:分别执行1,00,000,000次,统计时间;
* 测试环境:JDK1.8,Intel i3,双核四线程,2.13GHz,内存2GB
* */
public class ReflectTest {
public static void main(String[]args){
//普通调用
TestBeanRun normal=new TestBeanRun();
TestBeanCount.testBean(normal);
//
TestBeanRun norRef = new TestBeanRun() {
@Override
public void testConstruct() {
Class<?> clazz;
try {
// clazz = TestBean.class;
clazz=Class.forName("reflectTest.TestBean");
super.tb = (TestBean) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
public void testGet() {
try {
Method m = TestBean.class.getMethod("getWord");
s = (String) m.invoke(tb);
} catch (NoSuchMethodException | SecurityException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
}
}
@Override
public void testSet() {
try {
Method m = TestBean.class.getMethod("setWord",String.class);
m.invoke(tb, "testSet");
} catch (NoSuchMethodException | SecurityException
| IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
}
}
};
TestBeanCount.testBean(norRef);
TestBeanCount.testBean(new TestBeanRun(){
Class<?>clazz=TestBean.class;
Method mget;
Method mset;
{
try {
mget=TestBean.class.getMethod("getWord");
mset=TestBean.class.getMethod("setWord",String.class);
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
}
}
@Override
public void testConstruct() {
try {
super.tb=(TestBean) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public void testGet() {
try {
s=(String)mget.invoke(tb,null);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
}
}
@Override
public void testSet() {
try {
mset.invoke(tb,"testSet");
} catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
}
}
});
}
}
这里对每个单元测试了十遍,结果如下所示。
普通调用 | 普通反射 | 缓存的反射 | ||||||
method | time/ms | avr/ns | time/ms | avr/ns | time/ms | avr/ns | ||
construct | 970 | 9.70 | 145401 | 1454.01 | 2294 | 22.94 | ||
get | 637 | 6.37 | 30690 | 306.90 | 1536 | 15.36 | ||
set | 682 | 6.82 | 32830 | 328.30 | 2413 | 24.13 | ||
construct | 883 | 8.83 | 143928 | 1439.28 | 2122 | 21.22 | ||
get | 611 | 6.11 | 44981 | 449.81 | 1472 | 14.72 | ||
set | 707 | 7.07 | 55214 | 552.14 | 2382 | 23.82 | ||
construct | 893 | 8.93 | 251054 | 2510.54 | 2125 | 21.25 | ||
get | 619 | 6.19 | 52653 | 526.53 | 1443 | 14.43 | ||
set | 713 | 7.13 | 58730 | 587.30 | 2370 | 23.70 | ||
construct | 890 | 8.90 | 188971 | 1889.71 | 2131 | 21.31 | ||
get | 593 | 5.93 | 29949 | 299.49 | 1429 | 14.29 | ||
set | 656 | 6.56 | 32276 | 322.76 | 2400 | 24.00 | ||
construct | 901 | 9.01 | 141805 | 1418.05 | 2101 | 21.01 | ||
get | 577 | 5.77 | 30169 | 301.69 | 1452 | 14.52 | ||
set | 654 | 6.54 | 31594 | 315.94 | 2387 | 23.87 | ||
construct | 895 | 8.95 | 138948 | 1389.48 | 2144 | 21.44 | ||
get | 570 | 5.70 | 29739 | 297.39 | 1471 | 14.71 | ||
set | 649 | 6.49 | 31638 | 316.38 | 2403 | 24.03 | ||
construct | 864 | 8.64 | 137625 | 1376.25 | 2113 | 21.13 | ||
get | 596 | 5.96 | 30532 | 305.32 | 1427 | 14.27 | ||
set | 676 | 6.76 | 31447 | 314.47 | 2352 | 23.52 | ||
construct | 904 | 9.04 | 138463 | 1384.63 | 2143 | 21.43 | ||
get | 600 | 6.00 | 29677 | 296.77 | 1429 | 14.29 | ||
set | 674 | 6.74 | 31492 | 314.92 | 2383 | 23.83 | ||
construct | 889 | 8.89 | 139524 | 1395.24 | 2105 | 21.05 | ||
get | 597 | 5.97 | 29642 | 296.42 | 1426 | 14.26 | ||
set | 674 | 6.74 | 31579 | 315.79 | 2429 | 24.29 | ||
construct | 865 | 8.65 | 138246 | 1382.46 | 2128 | 21.28 | ||
get | 573 | 5.73 | 29879 | 298.79 | 1423 | 14.23 | ||
set | 652 | 6.52 | 31571 | 315.71 | 2386 | 23.86 |
有结果可知。在JDK1.8下,常规调用和反射调用的差距并没有那么大。普通的反射调用的速度很差,原因在于调用了Class.forName查找类很费时,getMethod查找方法也很费时。如果可以将类或者方法预先缓存下来再执行,也就是第三种方法,则效率差不太多了。