javaswitch-statementvoidjava-16switch-expression

Switch expression with void return type


Is there any way to force an exhaustive check of all enum values when the switch branches call methods with void return type? It's quite ugly to hard-code a yield just to coax the compiler to demand exhaustiveness.

This is my current pattern (the handle methods have void return type)

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; }
};

The reason I want to use an expression is to get a compilation error when a new enum value is added and is not handled.


Solution

  • Maybe yield a Consumer of Event, so you yield something useful, the trade off is one more line for 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);
    

    Continue if you concern performance

    Based on the comment concerning performance penalty, a benchmark is performed to compare following scenarios:

    1. Using consumer and handle is instance method
    2. Using consumer and handle is static method
    3. Not using consumer and handle is instance method
    4. Not using consumer and handle is static method

    To see

    And the result is:

    # 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
    
    

    By observing the result, there is no much different between the 4 scenarios.


    The benchmark is performed with:
    CPU: Intel(R) Core(TM) i7-8750H
    Memory: 16G
    JMH version: 1.19
    VM version: 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 {
        }
    }