类型为 void return 的 Switch 表达式
Switch expression with void return type
当 switch 分支调用具有 void return 类型的方法时,是否有任何方法强制对所有枚举值进行详尽检查?硬编码 yield 只是为了哄编译器要求详尽无遗是非常丑陋的。
这是我当前的模式(句柄方法有 void return 类型)
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; }
};
我想使用表达式的原因是在添加新枚举值但未处理时出现编译错误。
可能会产生 Event
的 Consumer
,所以你会产生一些有用的东西,权衡是 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是实例方法
- 使用consumer和handle是静态方法
- 不使用消费者和句柄是实例方法
- 不使用消费者和句柄是静态方法
去看看
- 使用 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: 英特尔(R) 酷睿(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, time = 500, 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 {
}
}
如果您在发布主代码之前构建了测试 classes(比如 JUNIT 测试用例)并且 运行,那么您可以将一个简单的保护函数放入任何现有测试中 class 对于您要观看的每个枚举:
String checkForEnumChanged(YourEnum guard) {
return switch (guard) {
case ORDER -> "OK";
case INVOICE -> "OK";
case PAYMENT -> "OK";
};
}
这意味着您可以使主应用程序代码远离 yield 0;
开关样式,并在编辑枚举值时在测试 classes 中出现编译错误。
问题的陈述有点“XY问题”;你想要的是完整性检查,但你要求将其视为表达式,不是因为你想要一个表达式,而是因为你想要表达式罩附带的完整性检查。
添加 switch 表达式留下的“技术债”之一是 switch 语句 能够选择与 switch 表达式进行相同的完整性检查。我们无法追溯更改有关 switch 语句的这一点——switch 语句一直被允许是部分的——但你是对的,如果能够进行这种类型检查会很好。正如您猜测的那样,将它变成一个无效的表达式开关是实现这一目标的一种方法,但它确实很难看,而且更糟的是,不容易被发现。我们的清单上有一种方法可以让您选择重新对 switch 语句进行完整性检查。 amber-spec-experts
列表上已经讨论过这个问题;它与其他几个可能的功能有关,设计讨论仍在进行中。
添加代表
添加一个委托方法来转发请求和return一个Void
类型
public class SwitchTest {
enum EventType {
ORDER,
INVOICE,
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
的其他答案所指出的那样,最好的选择似乎是使用 Consumer
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);
}
Runnable 怎么样:
Runnable limitOperationRunnable = switch (limitOperation) {
case INSERT -> () -> ...;
case UPDATE -> () -> ...;
case DELETE -> () -> ...;
};
limitOperationRunnable.run();
当 switch 分支调用具有 void return 类型的方法时,是否有任何方法强制对所有枚举值进行详尽检查?硬编码 yield 只是为了哄编译器要求详尽无遗是非常丑陋的。
这是我当前的模式(句柄方法有 void return 类型)
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; }
};
我想使用表达式的原因是在添加新枚举值但未处理时出现编译错误。
可能会产生 Event
的 Consumer
,所以你会产生一些有用的东西,权衡是 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是实例方法
- 使用consumer和handle是静态方法
- 不使用消费者和句柄是实例方法
- 不使用消费者和句柄是静态方法
去看看
- 使用 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: 英特尔(R) 酷睿(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, time = 500, 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 {
}
}
如果您在发布主代码之前构建了测试 classes(比如 JUNIT 测试用例)并且 运行,那么您可以将一个简单的保护函数放入任何现有测试中 class 对于您要观看的每个枚举:
String checkForEnumChanged(YourEnum guard) {
return switch (guard) {
case ORDER -> "OK";
case INVOICE -> "OK";
case PAYMENT -> "OK";
};
}
这意味着您可以使主应用程序代码远离 yield 0;
开关样式,并在编辑枚举值时在测试 classes 中出现编译错误。
问题的陈述有点“XY问题”;你想要的是完整性检查,但你要求将其视为表达式,不是因为你想要一个表达式,而是因为你想要表达式罩附带的完整性检查。
添加 switch 表达式留下的“技术债”之一是 switch 语句 能够选择与 switch 表达式进行相同的完整性检查。我们无法追溯更改有关 switch 语句的这一点——switch 语句一直被允许是部分的——但你是对的,如果能够进行这种类型检查会很好。正如您猜测的那样,将它变成一个无效的表达式开关是实现这一目标的一种方法,但它确实很难看,而且更糟的是,不容易被发现。我们的清单上有一种方法可以让您选择重新对 switch 语句进行完整性检查。 amber-spec-experts
列表上已经讨论过这个问题;它与其他几个可能的功能有关,设计讨论仍在进行中。
添加代表
添加一个委托方法来转发请求和return一个Void
类型
public class SwitchTest {
enum EventType {
ORDER,
INVOICE,
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
的其他答案所指出的那样,最好的选择似乎是使用 Consumer
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);
}
Runnable 怎么样:
Runnable limitOperationRunnable = switch (limitOperation) {
case INSERT -> () -> ...;
case UPDATE -> () -> ...;
case DELETE -> () -> ...;
};
limitOperationRunnable.run();