最近在项目中使用阿里编码规约插件对代码进行扫描,发现代码中有几处因switch分支较多导致违反如下规约(单个方法的总行数不超过80行)。
我这里想到了使用Java反射机制代替switch分支判断以缩减代码行。
Java反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。
因为单位项目代码保密,我这边使用demo代码代替讲解。
首先,虚构业务方法处理类:
public class BusinessMethod {
public void method1(int code) {
System.out.println("method code = " + code);
}
public void method2(int code) {
System.out.println("method code = " + code);
}
public void method3(int code) {
System.out.println("method code = " + code);
}
public void method4(int code) {
System.out.println("method code = " + code);
}
public void method5(int code) {
System.out.println("method code = " + code);
}
public void method6(int code) {
System.out.println("method code = " + code);
}
public void method7(int code) {
System.out.println("method code = " + code);
}
public void method8(int code) {
System.out.println("method code = " + code);
}
public void method9(int code) {
System.out.println("method code = " + code);
}
public void method10(int code) {
System.out.println("method code = " + code);
}
public void method11(int code) {
System.out.println("method code = " + code);
}
public void method12(int code) {
System.out.println("method code = " + code);
}
public void method13(int code) {
System.out.println("method code = " + code);
}
public void method14(int code) {
System.out.println("method code = " + code);
}
public void method15(int code) {
System.out.println("method code = " + code);
}
public void method16(int code) {
System.out.println("method code = " + code);
}
public void method17(int code) {
System.out.println("method code = " + code);
}
public void method18(int code) {
System.out.println("method code = " + code);
}
public void method19(int code) {
System.out.println("method code = " + code);
}
public void method20(int code) {
System.out.println("method code = " + code);
}
public void method21(int code) {
System.out.println("method code = " + code);
}
}
业务处理类中有测试方法method1到method21。
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
public class TestSwitchReflectDemo {
private static HashMap<Integer, String> sMaps;
static {
sMaps = new HashMap<Integer, String>();
sMaps.put(1, "method1");
sMaps.put(2, "method2");
sMaps.put(3, "method3");
sMaps.put(4, "method4");
sMaps.put(5, "method5");
sMaps.put(6, "method6");
sMaps.put(7, "method7");
sMaps.put(8, "method8");
sMaps.put(9, "method9");
sMaps.put(10, "method10");
sMaps.put(11, "method11");
sMaps.put(12, "method12");
sMaps.put(13, "method13");
sMaps.put(14, "method14");
sMaps.put(15, "method15");
sMaps.put(16, "method16");
sMaps.put(17, "method17");
sMaps.put(18, "method18");
sMaps.put(19, "method19");
sMaps.put(20, "method20");
sMaps.put(21, "method21");
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InstantiationException, InvocationTargetException {
int code = 11;
testSwitch(code);
testReflect(code);
}
public static void testReflect(int code) throws ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InstantiationException, InvocationTargetException {
long start = System.nanoTime();
Class clz = Class.forName("BusinessMethod");
Object obj = clz.newInstance();
Method m = clz.getDeclaredMethod(sMaps.get(code), Integer.TYPE);
m.setAccessible(true);
m.invoke(obj, code);
System.out.println("testReflect time:" + (System.nanoTime() - start));
}
public static void testSwitch(int code) {
BusinessMethod businessMethod = new BusinessMethod();
long start = System.nanoTime();
switch (code) {
case 1:
businessMethod.method1(code);
break;
case 2:
businessMethod.method2(code);
break;
case 3:
businessMethod.method3(code);
break;
case 4:
businessMethod.method4(code);
break;
case 5:
businessMethod.method5(code);
break;
case 6:
businessMethod.method6(code);
break;
case 7:
businessMethod.method7(code);
break;
case 8:
businessMethod.method8(code);
break;
case 9:
businessMethod.method9(code);
break;
case 10:
businessMethod.method10(code);
break;
case 11:
businessMethod.method11(code);
break;
case 12:
businessMethod.method12(code);
break;
case 13:
businessMethod.method13(code);
break;
case 14:
businessMethod.method14(code);
break;
case 15:
businessMethod.method15(code);
break;
case 16:
businessMethod.method16(code);
break;
case 17:
businessMethod.method17(code);
break;
case 18:
businessMethod.method18(code);
break;
case 19:
businessMethod.method19(code);
break;
case 20:
businessMethod.method20(code);
break;
case 21:
businessMethod.method21(code);
break;
default:
break;
}
System.out.println("testSwitch time:" + (System.nanoTime() - start));
}
}
TestSwitchReflectDemo中分别提供了switch测试方法和反射测试方法。
使用一种方案取代另外一种方案我们必须要知道新方案是否比原方案更优(起码也不能比原方案差很多)。
测试代码中为了避免switch分支判断耗时,取了这种的分支(case 11:)作为测试分支。
测试时为避免两个方法(testSwitch和testReflect)运行干扰,测试时分别单独测试五次结果如下:
从上述测试的结果上可以看到,反射执行的效率比switch分支判断要低。因为方法测试使用毫米无法区分差异,故测试时计时单位使用的为纳秒(ns)。
Java的反射API是有较高的性能开销的,这方面的性能比较文章较多,在此不赘述。
本文实测用例对反射性能做了优化:
1.使用Cache,对通过反射调用获得的Class、Method对实例进行缓存,避免多次Dynamic Resolve。
2.取消Java语言访问检查,如:方法反射时,setAccessible(true)。
因为本测试用例执行单次,所以反射时使用cache效果不是很明显。
修改反射测试方法(进行10W次):
public static void testReflect(int code) throws ClassNotFoundException, NoSuchMethodException,
IllegalAccessException, InstantiationException, InvocationTargetException {
long start = System.nanoTime();
Class clz = Class.forName("BusinessMethod");
Object obj = clz.newInstance();
Method m = clz.getDeclaredMethod(sMaps.get(code), Integer.TYPE);
m.setAccessible(true);
for (int i = 0; i < 100000; i++) {
m.invoke(obj, code);
}
System.out.println("testReflect time:" + (System.nanoTime() - start));
}
修改testSwitch方法(进行10W次switch分支测试):
public static void testSwitch(int code) {
BusinessMethod businessMethod = new BusinessMethod();
long start = System.nanoTime();
for (int i = 0; i < 100000000; i++) {
switch (code) {
case 1:
businessMethod.method1(code);
break;
case 2:
businessMethod.method2(code);
break;
case 3:
businessMethod.method3(code);
break;
case 4:
businessMethod.method4(code);
break;
case 5:
businessMethod.method5(code);
break;
case 6:
businessMethod.method6(code);
break;
case 7:
businessMethod.method7(code);
break;
case 8:
businessMethod.method8(code);
break;
case 9:
businessMethod.method9(code);
break;
case 10:
businessMethod.method10(code);
break;
case 11:
businessMethod.method11(code);
break;
case 12:
businessMethod.method12(code);
break;
case 13:
businessMethod.method13(code);
break;
case 14:
businessMethod.method14(code);
break;
case 15:
businessMethod.method15(code);
break;
case 16:
businessMethod.method16(code);
break;
case 17:
businessMethod.method17(code);
break;
case 18:
businessMethod.method18(code);
break;
case 19:
businessMethod.method19(code);
break;
case 20:
businessMethod.method20(code);
break;
case 21:
businessMethod.method21(code);
break;
default:
break;
}
}
System.out.println("testSwitch time:" + (System.nanoTime() - start));
}
测试结果如下:
1 2 3 4 5 均
平均到每次耗时差距就更小了。
备注:
电脑测试环境:Win10系统 CPU I7 DDR 8GB
小结:
1.反射有较高的性能开销的,如果允许毫秒级误差,这个性能开销可以忽略不计。
2..使用反射时可以针对性进行优化,提供反射执行效率。