2016-12-12 2 views
0

У меня есть алгоритм, который дает мне Integer. Основываясь на этом Integer, я хочу вызвать метод. Каждый Integer уникален (как первичный ключ в базе данных) и имеет 1 метод для вызова. Каждый метод возвращает один и тот же тип данных. Все методы находятся в одном классе и вызываются в этом классе.наиболее эффективный способ вызова определенного метода ключом || refactor method вызывающий оператор switch

После нескольких часов поиска я получаю только 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 
    } 

Является ли там может быть (?) лучший способ для моей проблемы или других решений? Если вы можете дать мне небольшой намек, я был бы очень рад.

+2

Другая большая проблема с отражением заключается в том, что это повредит статическому анализу. Вы не сможете попросить свою среду IDE показать вам все места, где вызывается 'doMethod1'. – yshavit

+1

Эти два способа отличаются. В первом вы вызываете методы на 'this', а во втором вы вызываете их на' nextOperation'. Второй также не меняет значение 'nextOperation'. – Bubletan

+1

Являются ли коды последовательными? Затем вы можете просто использовать массив (или, возможно, немодифицируемый список) либо 'MethodHandle', либо некоторые функции, вызывающие методы. – Bubletan

ответ

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 для вызова каждого метода.

Код для построения карты будет по-прежнему довольно продолжительным. Вы можете сократить его, используя отражение, чтобы помочь построить карту. Это будет медленнее, но вы платите штраф за отражение только один раз, когда карта была построена, а не каждый раз, когда вам нужно отправить метод по номеру.

Примечание: Вот еще один способ, чтобы добавить методы к карте без использования лямбды:

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()); компилятор видит, что это передается методу putMap<Integer, Runnable> и принимает анонимный метод как метод runRunnable и использует этот код для создания Runnable.

+0

Очень маловероятно, что карта изменится во время выполнения, если коммутатор является применимой альтернативой. – Bubletan

+0

Почему карта меняется во время выполнения? – Phil

+1

@Phil Я не знаю, что так будет. Я просто был очень осторожен, потому что не знаю, что может сделать остальная часть вашего кода. Лучший способ сделать это - создать карту один раз, а затем никогда не изменять ее, а только читать. –

3

Вы могли бы использовать:

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

И тогда звоните:

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

Замечание стиля: Я думаю, что либо '() -> doMethod1', либо' MyClass :: doMethod1' будет работать. – yshavit

+2

@yshavit 'MyClass :: doMethod1' будет работать только в том случае, если метод статичен. – shmosel

+1

Как насчет 'this :: doMethod1' –

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(); 
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.findStatic будет использоваться lookup.findVirtual.

Для вызова метода по номеру, где 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); 
+0

Спасибо за это другое хорошее решение и улучшение исполнения. (Никогда не слышал об «MethodHandle» раньше) Но почему я должен использовать карту, когда я получаю метод непосредственно с помощью 'mh.invokeExact()'? И кажется, что я не могу использовать 'MethodHandle' в Android, дыра' java.lang.invoke' не работала ... – Phil

+1

@Phil Я полагаю, вы могли бы искать это каждый раз, вместо того, чтобы хранить его на карте , Я не уверен, как это повлияет на производительность. В любом случае, похоже, вы не можете его использовать. Один с Runnables был примерно такой же скорости, как MethodHandles в некоторых тестах на моем ноутбуке. –

Смежные вопросы