通过键调用特定方法的最有效方法重构方法调用switch语句

时间:2016-12-12 22:36:05

标签: java methods reflection switch-statement refactoring

我有算法给我一个整数。 基于这个Integer,我想调用一个方法。 每个Integer都是唯一的(如数据库中的主键),并且有一个方法可以调用。每个方法都返回相同的数据类型。这些方法都在同一个类中,并在此类中调用。

经过几个小时的搜索,我只得到了这2个解决方案,但我不知道哪个是更好的"? (运行时间,资源)

切换解决方案: 第一个想法,但感觉不是很好

switch (code) {
    case 1:
        nextOperation = doMethod1();
        break;

    case 2:
        nextOperation = doMethod2();
        break;

    //many more cases...

    default:
        break;
    }

public MyObject doMethod1(MyObject myObject){
    //do something with operation
    return myObject;
    }

反射解决方案:可能运行时间不好(?)

    try{
        String methodName = "doMethod" + Integer.toString(operation.getOperationCode()); 
        //operation.getOperationCode() same like code in switch solution
        Method method = this.class.getDeclaredMethod(methodName, parametertype);
        nextOperation = (MyObject) method.invoke(this, parameter);
    }
    catch (Exception e){
        LogReport.writeLog(e.toString()); //own Log-Report filewriter
    }

对于我的问题或其他解决方案,是否有更好的方法? 如果你能给我一点提示,我会很高兴。

4 个答案:

答案 0 :(得分:5)

第三种选择是建立一个从数字到Runnables的地图,并查找要调用的方法。我不确定运行时间会如何比较,但我想它会比使用反射更快。

Map<Integer, Runnable> methodMap = new ConcurrentHashMap<>();
methodMap.put(1, () -> doMethod1());
methodMap.put(2, () -> doMethod2());
methodMap.put(3, () -> doMethod3());
// ... and so on ...

// look up the method and run it:
Runnable method = methodMap.get(code);
if (method != null) {
    method.run();
}

我正在使用ConcurrentHashMap以防万一这个地图需要在程序运行时动态修改,但是如果你在开始时只构建一次地图然后再修改它,普通的HashMap也可以这样做。我使用lambdas创建Runnables来调用每个方法。

构建地图的代码仍然很长。您可以通过使用反射来帮助构建地图来缩短它。这样会慢一些,但是你只需要在构建地图时支付一次反射罚款,而不是每次需要按编号调度方法。

注意:这是在不使用lambdas的情况下将方法添加到地图的另一种方法:

methodMap.put(1, new Runnable() { public void run() { doMethod1(); } });
methodMap.put(2, new Runnable() { public void run() { doMethod2(); } });
methodMap.put(3, new Runnable() { public void run() { doMethod3(); } });
// etc.

这就是匿名内部类的完成方式; lambdas本质上是匿名方法,不带参数()->之后的表达式是要调用的代码(例如doMethod1());编译器发现这是传递给put的{​​{1}}方法,并将匿名方法作为Map<Integer, Runnable>的{​​{1}}方法,并使用该代码创建run

答案 1 :(得分:3)

您可以使用:

Map<Integer, Supplier<Operation>> map = new ...;
map.put(1, () -> doMethod1());
map.put(2, () -> doMethod2());

然后致电:

nextOperation = map.get(operationCode).get();

答案 2 :(得分:1)

如果您使用的是Java 8,则可以尝试使用lambda调用方法,将它们映射到整数HashMap。

<强>接口

public interface MyInterface {
    void excecute();
}

初始化方法:

private int i = 0;
...
...
HashMap<Integer, MyInterface> myMap = new HashMap<>();
myMap.put(0, () -> {
    i = doMethod0();
});

调用方法:

myMap.get(i).excecute();

答案 3 :(得分:1)

另一种方法(与Java 7兼容,虽然我不知道Android是否支持)是使用MethodHandles。它们提供了反射的优势,因为您不必编写所有代码来填充映射(尽管可以编写脚本来生成代码,因为这可能只需要执行一次),但它们由于访问检查是在执行查找时提前执行的,因此比反射更快。

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.HashMap;
import java.util.Map;

Map<Integer, MethodHandle> handleByNumber = new HashMap<>();
MethodHandles.Lookup = MethodHandles.lookup();
MethodType mt = MethodType.methodType(MyObject.class, MyObject.class);

int number = 1; // find all doMethodN methods from 1 up to whatever
while (true) {
    try {
        MethodHandle mh = lookup.findStatic(MyClass.class, "doMethod" + number, mt);
        handles.put(number, mh);
        number++;
    } catch (NoSuchMethodException | IllegalAccessException e) {
        break;
    }
}

请注意,这假定方法是static,方法所在的类名为MyClass,并且方法采用并返回问题中的MyObject,但是这个问题并不一致。这些都必须改变。

如果方法不是静态的,则会使用lookup.findVirtual代替lookup.findStatic

按编号调用方法,其中obj是参数:

MethodHandle mh = handles.get(code);
if (mh != null) {
    try {
        MyObject result = (MyObject) mh.invokeExact(obj);
    } catch (Throwable e) {
        throw new RuntimeException(e); // or other handling as appropriate
    }
}

如果方法不是静态的,则需要在调用中提供接收者(您调用方法的对象):

MyObject result = (MyObject) mh.invokeExact(receiver, obj);