31

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

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

4

5 回答 5

21

也许收益 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);

如果您关心性能,请继续

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

  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

通过观察结果,这4个场景之间没有太大的不同。

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

执行基准测试:
CPU:Intel(R) Core(TM) i7-8750H
内存:16G
JMH 版本:1.19
VM 版本: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 {
    }
}
于 2021-02-15T09:22:52.943 回答
16

问题的陈述有点“XY问题”;你想要的是整体检查,但你要求它被视为一个表达式,不是因为你想要一个表达式,而是因为你想要表达式引擎附带的整体检查。

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

于 2021-02-16T00:49:56.207 回答
8

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

String checkForEnumChanged(YourEnum guard) {
    return switch (guard) {
        case ORDER -> "OK";
        case INVOICE -> "OK";
        case PAYMENT -> "OK";
    };
}

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

于 2021-02-15T12:28:39.137 回答
0

添加委托人

添加一个委托方法来转发请求并返回一个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,最好的选择似乎是使用消费者

    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);
    }
于 2021-02-20T07:51:46.623 回答
0

Runnable 怎么样:

Runnable limitOperationRunnable = switch (limitOperation) {
  case INSERT -> () -> ...;
  case UPDATE -> () -> ...;
  case DELETE -> () -> ...;
};
limitOperationRunnable.run();
于 2022-02-21T09:11:37.440 回答