如何解决返回类型为 void 的 switch 表达式
当 switch 分支调用返回类型为 void 的方法时,有没有办法强制对所有枚举值进行彻底检查?仅仅为了诱使编译器要求穷举而对 yield 进行硬编码是非常丑陋的。
这是我当前的模式(句柄方法有 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; }
};
我想使用表达式的原因是当一个新的枚举值被添加并且没有被处理时得到一个编译错误。
解决方法
也许会产生 Consumer
的 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);
如果您关心性能,请继续
根据有关性能损失的评论,执行基准测试以比较以下场景:
- 使用消费者和句柄是实例方法
- 使用消费者和句柄是静态方法
- 不使用消费者和句柄是实例方法
- 不使用消费者和句柄是静态方法
去看
- 使用 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
观察结果,4种情况没有太大区别。
- 使用 Consumer 不会对性能产生重大影响。
- 静态方法和实例
handle
方法之间的性能差异可以忽略不计。
基准是通过以下方式执行的:
CPU:Intel(R) Core(TM) i7-8750H
内存:16G
JMH 版本:1.19
虚拟机版本:JDK 15.0.2
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Warmup(iterations = 30,time = 500,timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 30,timeUnit = TimeUnit.MILLISECONDS)
public class SwitchExpressionBenchMark {
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
@Benchmark
public void consumerStaticHandle(Blackhole blackhole,InvoiceEvent invoiceEvent) {
Event event = invoiceEvent;
Consumer<Event> consumer = switch (event.getEventType()) {
case ORDER -> e -> staticHandle((OrderEvent) e);
case INVOICE -> e -> staticHandle((InvoiceEvent) e);
case PAYMENT -> e -> staticHandle((PaymentEvent) e);
};
consumer.accept(event);
}
@Benchmark
public void consumerHandle(Blackhole blackhole,InvoiceEvent invoiceEvent) {
Event event = invoiceEvent;
Consumer<Event> consumer = switch (event.getEventType()) {
case ORDER -> e -> this.handle((OrderEvent) e);
case INVOICE -> e -> this.handle((InvoiceEvent) e);
case PAYMENT -> e -> this.handle((PaymentEvent) e);
};
consumer.accept(event);
}
@Benchmark
public void noConsumerHandle(Blackhole blackhole,InvoiceEvent invoiceEvent) {
Event event = invoiceEvent;
int unused = switch (event.getEventType()) {
case ORDER -> {
this.handle((OrderEvent) event);
yield 0;
}
case INVOICE -> {
this.handle((InvoiceEvent) event);
yield 0;
}
case PAYMENT -> {
this.handle((PaymentEvent) event);
yield 0;
}
};
}
@Benchmark
public void noConsumerStaticHandle(Blackhole blackhole,InvoiceEvent invoiceEvent) {
Event event = invoiceEvent;
int unused = switch (event.getEventType()) {
case ORDER -> {
staticHandle((OrderEvent) event);
yield 0;
}
case INVOICE -> {
staticHandle((InvoiceEvent) event);
yield 0;
}
case PAYMENT -> {
staticHandle((PaymentEvent) event);
yield 0;
}
};
}
private static void staticHandle(PaymentEvent event) {
doSomeJob();
}
private static void staticHandle(InvoiceEvent event) {
doSomeJob();
}
private static void staticHandle(OrderEvent event) {
doSomeJob();
}
private void handle(PaymentEvent event) {
doSomeJob();
}
private void handle(InvoiceEvent event) {
doSomeJob();
}
private void handle(OrderEvent event) {
doSomeJob();
}
private static void doSomeJob() {
Blackhole.consumeCPU(16);
}
private enum EventType {
ORDER,INVOICE,PAYMENT
}
public static class Event {
public EventType getEventType() {
return eventType;
}
public void setEventType(EventType eventType) {
this.eventType = eventType;
}
private EventType eventType;
public double getD() {
return d;
}
public void setD(double d) {
this.d = d;
}
private double d;
}
public static class OrderEvent extends Event {
}
@State(Scope.Thread)
public static class InvoiceEvent extends Event {
@Setup(Level.Trial)
public void doSetup() {
this.setEventType(EventType.INVOICE);
}
}
public static class PaymentEvent extends Event {
}
}
,
问题的陈述有点像“XY问题”;您想要的是整体检查,但您要求将其视为表达式,不是因为您想要一个表达式,而是因为您想要表达式罩附带的整体检查。
添加 switch 表达式后留下的“技术债务”项目之一是 switch 语句 能够选择加入 switch 表达式获得的相同总体检查。我们无法追溯更改关于 switch 语句的这一点——switch 语句总是被允许是部分的——但是你是正确的,能够进行这种类型检查会很好。正如您所猜测的那样,将它变成一个空的表情开关是一种方法,但它确实很丑,更糟糕的是,不容易被发现。在我们的列表中,可以找到一种方法来允许您选择重新进行 switch 语句的总体检查。已经在 amber-spec-experts
列表中讨论过这个问题;它与其他几个可能的功能有关,设计讨论仍在进行中。
如果您有在发布主代码之前构建和运行的测试类(比如 JUNIT 测试用例),那么您可以将一个简单的保护函数放入任何现有的测试类中,以查看您想要查看的每个枚举:
String checkForEnumChanged(YourEnum guard) {
return switch (guard) {
case ORDER -> "OK";
case INVOICE -> "OK";
case PAYMENT -> "OK";
};
}
这意味着您可以让主应用程序代码远离 yield 0;
样式的开关,并在编辑枚举值时在测试类中获得编译错误。
添加委托
添加委托方法转发请求并返回Void
类型
public class SwitchTest {
enum EventType {
ORDER,PARCELDELIVERY
}
interface Event {
EventType getType();
}
static class OrderType implements Event {
@Override
public EventType getType() {
return EventType.ORDER;
}
}
static class InvoiceType implements Event {
@Override
public EventType getType() {
return EventType.INVOICE;
}
}
static void handle(Event e) {
System.out.println(e.getType());
}
static Void switchExpressionDelegate(Event e) {
handle(e);
return null;
}
public static void main(String[] args) {
Event event = new OrderType();
Void nullNoop = switch (event.getType()) {
case ORDER -> switchExpressionDelegate(event);
case INVOICE -> switchExpressionDelegate(event);
case PARCELDELIVERY -> switchExpressionDelegate(event);
};
}
}
确切类型
假设 handle
方法具有精确类型,则必须添加委托方法的并行层次结构。 (虽然这看起来不太好)
static Void switchExpressionDelegate(OrderType e) {
handle(e);
return null;
}
static Void switchExpressionDelegate(InvoiceType e) {
handle(e);
return null;
}
public static void main(String[] args) {
Event event = new OrderType();
Void nullNoop = switch (event.getType()) {
case ORDER -> switchExpressionDelegate((OrderType) event);
case INVOICE -> switchExpressionDelegate((InvoiceType) event);
case PARCELDELIVERY -> switchExpressionDelegate((OrderType) event); // can throw error in an actual implementation
};
}
适配器
如果添加新类是一个选项,那么可以添加适配器类
以上所有内容都差不多
正如 sambabcde
的其他回答所指出的,最好的选择似乎是使用消费者
public static void main(String[] args) {
Event event = new OrderType();
Consumer<Void> nullNoop = switch (event.getType()) {
case ORDER -> e -> handle((OrderType) event);
case INVOICE -> e -> handle((InvoiceType) event);
case PARCELDELIVERY -> e -> handle((OrderType) event);
};
nullNoop.accept(null);
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。