返回类型为 void 的 switch 表达式

arb*_*ark 30 java void switch-statement switch-expression java-16

当 switch 分支调用具有 void 返回类型的方法时,有没有办法强制对所有枚举值进行彻底检查?仅仅为了诱使编译器要求穷举而对产量进行硬编码是非常丑陋的。

这是我当前的模式(句柄方法具有 void 返回类型)

int unused = switch (event.getEventType()) {
    case ORDER   -> { handle((OrderEvent) event); yield 0; }
    case INVOICE -> { handle((InvoiceEvent) event); yield 0; }
    case PAYMENT -> { handle((PaymentEvent) event); yield 0; }
};
Run Code Online (Sandbox Code Playgroud)

我想使用表达式的原因是在添加新枚举值但未处理时出现编译错误。

sam*_*cde 21

也许 yield a Consumerof Event,所以你产生了一些有用的东西,权衡是多一行consumer.accept

Consumer<Event> consumer = switch (event.getEventType()) {
    case ORDER -> e -> handle((OrderEvent) e);
    case INVOICE -> e -> handle((InvoiceEvent) e);
    case PAYMENT -> e -> handle((PaymentEvent) e);
};
consumer.accept(event);
Run Code Online (Sandbox Code Playgroud)

如果您关心性能,请继续

根据有关性能损失的评论,执行基准测试以比较以下场景:

  1. 使用消费者和句柄是实例方法
  2. 使用消费者和句柄是静态方法
  3. 不使用消费者和句柄是实例方法
  4. 不使用消费者和句柄是静态方法

查看

  • 使用 Consumer 对性能有很大影响吗?
  • 静态handle方法和实例方法有什么区别吗?

结果是:

# Run complete. Total time: 00:20:30

Benchmark                                          Mode  Cnt      Score     Error   Units
SwitchExpressionBenchMark.consumerHandle          thrpt  300  49343.496 ±  91.324  ops/ms
SwitchExpressionBenchMark.consumerStaticHandle    thrpt  300  49312.273 ± 112.630  ops/ms
SwitchExpressionBenchMark.noConsumerHandle        thrpt  300  49353.232 ± 106.522  ops/ms
SwitchExpressionBenchMark.noConsumerStaticHandle  thrpt  300  49496.614 ± 122.916  ops/ms

Run Code Online (Sandbox Code Playgroud)

通过观察结果,4种场景之间没有太大区别。

  • 使用 Consumer 不会对性能产生重大影响。
  • 静态handle方法和实例方法之间的性能差异是可以忽略的。

基准测试使用:
CPU:Intel(R) Core(TM) i7-8750H
内存:16G
JMH 版本:1.19
VM 版本:JDK 15.0.2

Consumer<Event> consumer = switch (event.getEventType()) {
    case ORDER -> e -> handle((OrderEvent) e);
    case INVOICE -> e -> handle((InvoiceEvent) e);
    case PAYMENT -> e -> handle((PaymentEvent) e);
};
consumer.accept(event);
Run Code Online (Sandbox Code Playgroud)

  • @BrianGoetz 仅当这些“handle”方法是“static”时 (9认同)
  • @ArborealShark 此特定示例中的所有 lambda 都是非捕获的,因此实例将被记忆并缓存在捕获站点,性能开销为零。 (8认同)
  • 我是否错误地认为该解决方案通过在每次执行期间分配 lambda 函数对性能产生负面影响?如果 switch 表达式位于关键热路径中,则它可能很重要。 (2认同)
  • 注意,Score值的差异都是在报告的Error的数量级上,所以实际的结论是它们基本上都是相同的。或者需要更好的测试设置。 (2认同)
  • 不使用 doSomeJob() 的返回值可能会影响结果(使用 JMH 的黑洞来消耗该值)。除此之外,您可以尝试使用不同的参数,例如预热,看看它们是否对结果有影响。如果这一切都没有改变结果,则可能只是方法没有显着差异。 (2认同)

Bri*_*etz 16

问题的陈述有点像“XY问题”;您想要的是完整性检查,但您要求将其视为表达式,不是因为您想要表达式,而是因为您想要表达式罩附带的完整性检查。

添加 switch 表达式后留下的“技术债务”项目之一是 switch语句能够选择与 switch 表达式相同的总体检查。我们无法追溯更改关于 switch 语句的这一点——switch 语句总是被允许是部分的——但是你是对的,能够进行这种类型检查是很好的。正如您所猜测的那样,将它变成一个无效的表情开关是一种方法,但它确实很丑,更糟糕的是,不容易被发现。在我们的列表中,可以找到一种方法来允许您选择重新进行 switch 语句的总体检查。已经在amber-spec-experts名单上讨论过这个问题;它与其他几个可能的功能有关,设计讨论仍在进行中。


Dun*_*ncG 8

如果您有在发布主代码之前构建和运行的测试类(比如 JUNIT 测试用例),那么您可以将一个简单的保护函数放入任何现有的测试类中,以查看您想要查看的每个枚举:

String checkForEnumChanged(YourEnum guard) {
    return switch (guard) {
        case ORDER -> "OK";
        case INVOICE -> "OK";
        case PAYMENT -> "OK";
    };
}
Run Code Online (Sandbox Code Playgroud)

这意味着yield 0;当编辑枚举值时,您可以使您的主应用程序代码远离switch 样式,并在测试类中得到编译错误。

  • @Naman此代码不适用于OP应用程序,它会导致测试代码中的编译失败,从而警告OP需要处理主代码库中使用的非yield开关中的枚举更改。 (7认同)