尝试在 Java 中使用 lambda 进行较小的重构

Trying to do a minor refactor using lambdas in Java

我正在编写一个程序的一部分(关于语音识别和遥控车),其中代码 transmit(XXXXX); disableAutoMode(); 重复了很多次。出于好奇,我想将其转换为类似于 var f = p -> transmit(p); disableAutoMode(); 的 lambda 函数(请原谅 var;我不知道表达式的类型是什么),然后在与此类似的方式:f("s");f("a");f("f"); 或类似于 f.call("s");f.call("a");f.call("f");.

的方式

在 Java 中使用类似于我上面描述的简单 lambda 函数的正确语法是什么? (我应该写什么而不是说 var?)

这是代码块,如果你好奇的话:

@Override
public void onResult(Hypothesis hypothesis) {
    if (hypothesis != null) {
        String text = hypothesis.getHypstr();
        Log.i("onresult",text);
        ToastMaster(text);

        switch (text) {
            case "forward":
            case "go forward":
                transmit("f");
                disableAutoMode();
                break;
            case "go back":
            case "go backward":
            case "back":
            case "backward":
            case "reverse":
                transmit("b");
                disableAutoMode();
                break;
            case "skid left":
            case "go left":
                transmit("l");
                disableAutoMode();
                break;
            case "skid right":
            case "go right":
                transmit("r");
                disableAutoMode();
                break;
            case "turn left":
                transmit("q");
                disableAutoMode();
                break;
            case "turn right":
                transmit("e");
                disableAutoMode();
                break;
            case "reverse left":
                transmit("z");
                disableAutoMode();
                break;
            case "reverse right":
                transmit("x");
                disableAutoMode();
                break;
            case "stop":
                disableAutoMode();
                break;
            case "automatic":
                toggleAutoMode(null);
                break;
            case "enable automatic mode":
                enableAutoMode();
                break;
            case "disable automatic mode":
                disableAutoMode();
                break;
        }
    }
}

调用 lambda 会比使用简单的辅助方法更乏味:

private void m(String x) {
    transmit(x);
    disableAutoMode();
}

一个(更详细的)替代方法是使用 lambda:

Consumer<String> consumer = (x) -> {
    transmit(x);
    disableAutoMode();
};

然后调用它类似于

consumer.accept("f");

在这种情况下,您需要 Consumer

Consumer<String> function = (x) -> { transmit(x); disableAutoMode(); };
function.accept("hello!");

但是我不确定你为什么要在这里使用 lambda 表达式,你可以创建一个普通的旧方法并调用它。

如果您希望进行更有意义的重构,一种选择是切换到 StringAction/Runnable 的映射。虽然您最终会得到更多代码,但重构的目标不是“使其更小”,而是使其更多readable/maintainable。通过将每个动作拆分成自己的小独立 class,每个动作都可以用最少的设置进行隔离测试。每个动作都可以重复使用(因为它只是一个 class)。有了好的命名策略,读者就可以一目了然,而无需深入研究大量的 switch 语句。

你想做的,用lambda表达式确实可以,但节省的不会那么多:

@Override
public void onResult(Hypothesis hypothesis) {
    if (hypothesis != null) {
        String text = hypothesis.getHypstr();
        Log.i("onresult",text);
        ToastMaster(text);

        Consumer<String> transmitDisable=s->{ transmit(s); disableAutoMode(); };
        switch (text) {
            case "forward": case "go forward":
                transmitDisable.accept("f");
                break;
            case "go back":  case "go backward": case "back":
            case "backward": case "reverse":
                transmitDisable.accept("b");
                break;
            case "skid left": case "go left":
                transmitDisable.accept("l");
                break;
            case "skid right": case "go right":
                transmitDisable.accept("r");
                break;
            case "turn left":     transmitDisable.accept("q"); break;
            case "turn right":    transmitDisable.accept("e"); break;
            case "reverse left":  transmitDisable.accept("z"); break;
            case "reverse right": transmitDisable.accept("x"); break;
            case "stop": disableAutoMode(); break;
            case "automatic": toggleAutoMode(null); break;
            case "enable automatic mode": enableAutoMode(); break;
            case "disable automatic mode": disableAutoMode(); break;
        }
    }
}

但是如果您考虑 Java:

的不常用代码流控制结构,您也可以删除没有 lambda 表达式的代码重复
@Override
public void onResult(Hypothesis hypothesis) {
    if (hypothesis != null) {
        String text = hypothesis.getHypstr();
        Log.i("onresult",text);
        ToastMaster(text);

        transmitAndDisable: {
            final String toTransmit;
            switch (text) {
                case "forward":    case "go forward": toTransmit="f"; break;
                case "go back":    case "go backward": case "back":
                case "backward":   case "reverse":  toTransmit="b"; break;
                case "skid left":  case "go left":  toTransmit="l"; break;
                case "skid right": case "go right": toTransmit="r"; break;
                case "turn left":     toTransmit="q"; break;
                case "turn right":    toTransmit="e"; break;
                case "reverse left":  toTransmit="z"; break;
                case "reverse right": toTransmit="x"; break;

                case "stop": disableAutoMode(); break transmitAndDisable;
                case "automatic": toggleAutoMode(null); break transmitAndDisable;
                case "enable automatic mode": enableAutoMode(); break transmitAndDisable;
                case "disable automatic mode": disableAutoMode(); break transmitAndDisable;
                default: break transmitAndDisable;
            }
            transmit(toTransmit);
            disableAutoMode();
        }
    }
}

结构可能不太容易阅读,但请注意将 toTransmit 声明为 final 有很大帮助。因为它是 final,它不能用回退值初始化,这意味着每个进入共享 transmit(…); disableAutoMode(); 代码的替代方案必须恰好初始化变量一次。

换句话说,

  • 如果你忘记在一个不打算调用共享代码的case的break处指定transmitAndDisable标签,编译器会立即大喊大叫,因为toTransmit没有已初始化。
  • 如果您忘记了要调用共享代码的备选方案之间的中断,编译器将立即拒绝该代码,因为它分配了两次 toTransmit
  • 如果您在要调用共享代码的案例的 break 处错误地指定了 transmitAndDisable 标签,好的编译器会发出有关未使用赋值的警告

更雄心勃勃的重构将建立在 lambda 所涉及的 "turn code into data" 原则之上,同时将您的 switch 语句从代码转换为数据。怎么样:

// One-time setup of the machine
Map<String, Consumer<String>> actions = new HashMap<>();
actions.put("go forward", x -> { transmit(x); disableAutoMode(); });
actions.put(...)
...

public void onResult(Hypothesis hypothesis) {
    if (hypothesis != null) {
        String text = hypothesis.getHypstr();
        Log.i("onresult",text);
        ToastMaster(text);

        Consumer<String> action = actions.get(text);
        if (action != null)
            action.accept(text);
    }
}