类型为 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; }
};

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

可能会产生 EventConsumer,所以你会产生一些有用的东西,权衡是 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);

如果您关心性能,请继续

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

  1. 使用consumer和handle是实例方法
  2. 使用consumer和handle是静态方法
  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

观察结果,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();