New JEP: Switch Expressions for the Java Language
Stephen Colebourne
scolebourne at joda.org
Mon Dec 11 12:04:32 UTC 2017
At first, my reaction was "great!", but after doing some analysis I'm
left feeling this isn't going to work well as proposed...
To summarise the proposal, expression switches get:
- comma separated OR values
- case null
- no fall through
- enforced default
- auto default clause for enums
And statement switches get none of these (AFAICT).
But if expression switches have no requirement to use the return value
(as per the rest of Java), then an expression switch can be used in
all circumstances that a statement switch is used today. And since the
expression switch would essentially be better than the statement
switch in every way, I believe best practice would effectively
deprecate statement switches in most cases. This appears to mean that
switch is being bifurcated, despite the explicit desire not to. So,
I've taken a look at what the impact of that on our codebase would be:
---
I examined the OpenGamma private codebase to see how we use switch.
The vast majority of uses are from Joda-Beans as follows:
switch (propertyName) {
case "firstLayer":
return firstLayer;
case "secondLayerType":
return secondLayerType;
default:
throw new NoSuchElementException("Unknown property: " + propertyName);
}
This pattern could be converted to the proposed expression switch,
which would be safer, but not necessarily easier to read (readability
varies depending on how short the expression is).
There were 111 examples that were not Joda-Beans generated. These can
be classified as follows:
- Mostly (maybe 80%+) "switch and return a value, throw if unknown" (as above).
- Some examples (maybe 10%) which were dying to be expression switches
(assign a local variable and set it up using the switch).
- Occasional example where two or three variables were being assigned
by the switch (something that expression switch could not handle
without using a messy Pair/Triple).
- A few examples where the switch is to cause the program flow to
dispatch to disparate pieces of logic. These cases had to use "break",
but would not be easily convertible to expression switches. A similar
example is where a Map or List was being updated by the switch.
- One case where it is essentially an expression switch, but the
default clause returns from the method instead of throwing.
- One case using fall through. Meh.
For info, here is a more tricky one that uses a mutable local variable:
private static Pattern wildcardsToPattern(String text) {
StringTokenizer tkn = new StringTokenizer(text, "?*", true);
StringBuilder buf = new StringBuilder(text.length() + 10);
buf.append('^');
boolean lastStar = false;
while (tkn.hasMoreTokens()) {
String str = tkn.nextToken();
switch (str) {
case "?":
buf.append('.');
lastStar = false;
break;
case "*":
if (!lastStar) {
buf.append(".*");
}
lastStar = true;
break;
default:
buf.append(Pattern.quote(str));
lastStar = false;
break;
}
}
buf.append('$');
return Pattern.compile(buf.toString(), Pattern.CASE_INSENSITIVE);
}
Having looked at our examples though, a lot of the common 80%+ group
involve a block of code for each case, not a single expression. These
would be converted to expression switches as follows;
private String someMethod(String foo) {
return switch (foo) {
case "a", "b" -> {
// do something
return "AAA";
}
case "c", "d" -> {
// do something else
return "ZZZ";
}
default -> {
throw new IllegalArgumentException();
}
}
}
The key thing to note is that the suggested syntax results in "return
return", where you need two returns to escape the expression block. I
can't help but feel this isn't ideal in syntax terms. (I've tried
converting some existing code, and it looks pretty ugly).
More importantly, it doesn't feel ideal in semantic terms.
Specifically, it feels that all the new goodies in the expression form
will result in too much of a pull to use the expression switch when
the statement switch might actually be a better fit. ie. the code
above would be just fine as a statement switch, if only statement
switches had safer semantics.
To summarise - having looked at our uses of switch, I think that
adding expression switch as proposed (without enhancements to
statement switch) might well cause migration to expression switch that
makes code read worse, just to get the better semantics, particularly
wrt no fall through. Really, we should be converting only 10% of our
switch statements - the ones that genuinely should be expressions. But
with the current proposal, we might feel compelled to convert 98% to
get the extra safety.
** Given this situation, I think it is essential for the statement
switch to be enhanced alongside the addition of expression switch. **
(sorry!)
I'll avoid discussing enhanced statement switch syntax, but I do think
it would need to support all five of the new features (comma separated
OR, case null, no fall through, enforced default, auto-default for
enum). Given this, it would seem to me that perhaps expression switch
should not have blocks. We pretty much never use them with lambdas.
---
Finally, on the auto-default for enums, I think this is good. But it
is slightly odd that enums are special. I think a case can be made
that there should always be an auto-default that throws an exception.
It doesn't change what developers would have to write in most cases,
but would be more consistent.
Stephen
On 7 December 2017 at 22:30, Brian Goetz <brian.goetz at oracle.com> wrote:
> In the context of Pattern Matching (JEP 305) we've explored a number of
> improvements to `switch`, including switch expressions, expanding the range
> of types that can be arguments to switch, better null handling, and others.
> Many of these can be developed independently of pattern matching; we've
> separated out a package of these features into their own JEP, as they stand
> well on their own:
>
> https://bugs.openjdk.java.net/browse/JDK-8192963
>
>
More information about the amber-dev
mailing list