Existe-t-il un moyen de forcer une vérification exhaustive de toutes les valeurs d'énumération lorsque le commutateur branche les méthodes d'appel avec type de retour void? Il est assez laid de coder dur un rendement juste pour amadouer le compilateur pour exiger son exhaustivité.
C'est mon modèle actuel (les méthodes de poignée ont un type de retour vide)
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; } };
La raison pour laquelle je souhaite utiliser une expression est d'obtenir une erreur de compilation lorsqu'une nouvelle valeur d'énumération est ajoutée et n'est pas gérée.
5 Réponses :
peut-être produire un Consumer
de event
, donc vous donnez quelque chose d'utile, le compromis est une ligne de plus pour Consumer.Accept
. xxx
sur la base du commentaire concernant la pénalité de performance, une référence est effectuée pour comparer les scénarios suivants:
pour voir
Handle
? et le résultat est:
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 { } }
En observant le résultat, il n'y a pas beaucoup de différent entre les 4 scénarios.
La référence est effectuée avec:
CPU: Intel (R) Core (TM) I7-8750H
Mémoire: 16g
Version JMH: 1.19
VERSE VM: JDK 15.0.2
# 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
Ai-je tort de supposer que cette solution a un impact négatif sur les performances en allouant une fonction lambda pendant chaque exécution? Si l'expression du commutateur est dans un chemin chaud critique, cela peut être significatif.
@Arborealshark Tous les lambdas dans cet exemple particulier sont non capturés, et donc les instances seraient mémorisées et mises en cache sur le site de capture, avec des surcharges de performances zéro.
@Briangoetz uniquement lorsque ces méthodes manchent
sont static
Notez que les différences des valeurs de score sont toutes dans l'ordre de grandeur de l'erreur signalée, donc la conclusion réelle est qu'elles sont essentiellement identiques. Ou qu'une meilleure configuration de test est nécessaire.
@Holger, merci pour votre commentaire. Avez-vous une idée de ce qui est nécessaire de "Meilleure configuration"? Est-il dû à la longue durée d'exécution de dosomejob
faisant la différence (en raison de l'utilisation du consommateur) et non de l'apparence (la pénalité de performance est petite et cachée par erreur)? Ou je devrais essayer de changer le paramètre de référence?
Le fait que la valeur de retour de dosomeJob ()
ne soit pas utilisé peut affecter le résultat (utiliser le trou noir de JMH pour consommer la valeur). En plus de cela, vous pouvez essayer avec différents paramètres, par exemple Échauffement, pour voir s'ils ont un impact sur le résultat. Si tout ne change pas les résultats, il peut être simplement le cas que les approches ne diffèrent pas de manière significative.
Si vous avez des classes de test (disons les cas de test JUnit) que vous créez et exécutez avant de publier votre code principal, vous pouvez déposer une fonction de garde simple dans n'importe quelle classe de test existante pour chaque énumération que vous souhaitez regarder:
String checkForEnumChanged(YourEnum guard) { return switch (guard) { case ORDER -> "OK"; case INVOICE -> "OK"; case PAYMENT -> "OK"; }; }
Cela signifie que vous pouvez garder votre code d'application principal clair sur le style Rendement 0;
Switch et obtenir une erreur de compilation dans les classes de test lorsque les valeurs d'énumération sont modifiées. p>
Mais effectivement, vous cédez maintenant String
et la question sur eux est inutilisée est ce qui concerne l'OP, également un test unitaire peut sans utiliser l'expression de Switch suffise également pour garder le comportement, la question est liée Pour compiler le temps.
@NAMAN Ce code n'est pas destiné à être utilisé dans l'application OP, il est là pour provoquer l'échec de la compilation dans le code de test qui alerte OP de besoin de gérer le changement d'énumération des commutateurs non de rendement utilisés dans la base de code principale.
L'énoncé de la question est un peu un "problème xy"; Ce que vous voulez, c'est la vérification de la totalité, mais vous demandez qu'il soit traité comme une expression, non pas parce que vous voulez une expression, mais parce que vous voulez la vérification de la totalité qui vient avec une expression.
L'un des éléments de la "dette technique" laissée à partir de l'ajout d'expressions de commutation est la possibilité de Switch instructions pour opter pour la même vérification de la totalité que les expressions de commutation obtiennent. Nous n'avons pas pu modifier rétroactivement les instructions de commutation - les instructions de commutation ont toujours été autorisées à être partielles - mais vous avez raison de dire qu'il serait bien de pouvoir obtenir ce type de vérification de type. Comme vous le pensez, le transformer en un interrupteur d'expression vide est une façon d'y arriver, mais il est en effet laid, et pire, ne sera pas facilement découvrable. C'est sur notre liste de trouver un moyen de vous permettre de revenir sur la vérification totale des instructions de commutation. Il y a eu des discussions sur la liste Amber-Spec-Experts
à ce sujet; Il est lié à plusieurs autres fonctionnalités possibles, et les discussions de conception sont toujours en cours.
Ajoutez une méthode de délégué pour transférer la demande et renvoyer un void
Type
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); }
En supposant que la méthode manche
a un type exact, alors une hiérarchie parallèle des méthodes de délégué doit être ajoutée. (Cela ne semble pas bien cependant)
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 }; }
Si l'ajout de nouvelles classes est une option, alors les classes d'adaptateur peuvent être ajoutées
Tout ce qui précède semble autour de
Comme indiqué par une autre réponse par sambabcde
, la meilleure option semble être d'utiliser un consommateur
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); }; } }
Que diriez-vous de runnable:
Runnable limitOperationRunnable = switch (limitOperation) { case INSERT -> () -> ...; case UPDATE -> () -> ...; case DELETE -> () -> ...; }; limitOperationRunnable.run();
Vous utilisez une expression de commutation où vous devriez utiliser une instruction Switch.
@NomAdmaker Le but de l'expression est d'obtenir une erreur de compilation lorsqu'une nouvelle valeur d'énumération est ajoutée.
Le
Consumer
pourrait être accompagné des suggestions faites à Comment assurer l'exhaustivité dans un commutateur d'énumération au moment de la compilation? . Voir aussi le modèle des visiteurs décrit ici .Compte tenu de votre modification, qu'en est-il de donner le casting
événement
, puis d'appelermanche
passer l'expressionSwitch
lui-même comme un argument?Êtes-vous en mesure de refactor
Event
pour quemanche ()
y soit déplacé? Alors aucun commutateur n'est nécessaire, justeevent.handle ()
.Certains IDE et autres outils ont une option pour émettre des avertissements pour les commutateurs non exhaustifs. Dans Eclipse, cela est contrôlé avec Préférences> Java> Compiler> Erreurs / avertissements> Cas «Switch» incomplètes sur Enum .