Java 中按名称动态调用函数的最惯用方法是什么?

What's the most idiomatic way in Java to dynamically call a function by name?

我有一组特定的操作,我希望能够通过名称动态访问这些操作。

如果我使用 JavaScript,我会在字典中表示它们,操作名称作为键,操作函数作为值。

然后,例如,我可以向用户询问操作的名称,如果存在则显示操作结果,如果不存在则显示错误消息,如下所示:

var operations = {
  addition: function(a, b) { return a + b; },
  subtraction: function(a, b) { return a - b; },
  multiplication: function(a, b) { return a * b; }
  // etc.
};

var a = 4, b = 7;
var opName = prompt("Enter operation name:");
if (opName in operations) {
  alert("Result: " + operations[opName](a, b));
} else {
  alert("The operation '" + opName + "' does not exist.");
}

我如何在 Java 中做同样的事情?我可以有一个 Set<String> 的操作名称和一个函数,该函数使用带有 case 的 switch 来处理每个操作,但这需要我将每个操作名称重复两次,这使得代码更脆弱,编写和维护起来也更乏味.

在 Java 中是否有针对此类事情的相当简洁的 DRY 模式?

public interface Function {
    double run(double a, double b);
}

public class addFunction implements Function {
    double run(double a, double b) {
        return a+b;
    }
}
//...
Map<String, Function> operations = new HashMap<string, Function>();
operations.put("add", new addFunction());
//...
String op;
double a, b;
operations.get(op).run(a, b);

您可以在 Java 中执行相同的操作,而无需使用 Java8:

public interface Operation<T,R> {
    R perform(T... args);
}

public void test() {
    Map<String, Operation> operations = new HashMap<String, Operation>() {
        {
            this.put("addition", new Operation<Integer, Integer>() {
                public Integer perform(Integer... args) {
                    return args[0] + args[1];
                }});
        }
    };

    String operation = "";
    Integer a = 1;
    Integer b = 1;
    if (operations.containsKey(operation)) {
        System.out.println("Result: " + operations.get(operation).perform(a, b));
    } else {
        System.out.println("The operation '" + operation + "' does not exist.");
    }
}

如果您愿意,也可以将匿名 class 移动到单独的文件中。

如果您需要不同类型的参数,您将不得不使用泛型或将参数类型更改为 Object 然后进行强制转换。不漂亮,但这是静态类型的代价。

此外,编译器会向您发出警告(使用原始 Operation),但如果您想在同一个映射中存储不同类型的操作,则无需在此做太多事情。一个解决办法是为不同类型制作几张地图。

在 Java 8 中使用 lambda 更简洁:

Map<String, BinaryOperator<Integer>> operators = new TreeMap<>();
operators.put("add", (n1, n2) -> n1 + n2);
operators.put("minus", (n1, n2) -> n1 - n2);

if (operators.containsKey(opName)) {
    return operators.get(opName).apply(n1, n2);
}

但我从您的评论中了解到,这不是一种选择。另一种方法是使用枚举来包含您的操作,以便您可以在一个地方添加新操作:

enum Operation {
    PLUS {
        public int operate(int arg1, int arg2) {
            return arg1 + arg2;
        }
    },
    MINUS {
        public int operate(int arg1, int arg2) {
            return arg1 - arg2;
        }
    },
    ...

    abstract public int operate(int arg1, int arg2);
}

for (operation: Operations.values()) {
    if (operation.name().equals(opName))
        System.out.println("result = " + operation.operate(arg1, arg2));
        return;
    }
}
System.out.println("The Operation " + opName + " does not exist");