Recent versions of java (12 and up) have been putting in a ton of work on switch
. There are now quite a few forms. Some of these forms have an underlying principle that strongly suggests some new syntactical feature is important, and for consistency's sake, if that feature can also be allowed for the other forms, the other forms are updated too.
History
The original java 1.0 switch is a straight copy from C (which explains their utterly bizarre and silly syntax; it's what C had, and java was designed to be comfortable to C coders. We can whinge about the fact that this means the syntax is daft, but it's hard to deny that the C style syntax, no matter how crazy it seems, has taken over the planet, with C, C#, Java, and Javascript added together taking up a huge chunk of the 'market' so to speak).
It:
- Required that the thing you switch is on an
int
and only an int
- That each case is a specific and constant number (no
case 10-100
, and definitely no case <5
or case x:
).
- It's a statement (The switch itself doesn't have a value, it's a way to conditionally jump to a statement).
- Unlike the C version, treating the switch's nature as a weird GOTO-esque construct and thus allowing you to interleave a control structure such as a
do/while
loop through it is not actually allowed (fortunately, I guess)? - So, Duff's Device is not and has never been legal in java.
Recent updates:
Together with enums: Enums in switches
Java1.5 brought enums, and as enums are intended to replace int constants, they needed to be usable in switches. Given that enum values have an 'ordinal' which is an int, the implementation is trivial: switch (someEnum) { case ENUMVAL: }
is light sugar around switch(someEnum.ordinal()) { case 1: }
, where 1 is the ordinal value of ENUMVAL.
Only semi-recent: Strings
In JDK7, strings-in-switch support was added. The same principles apply: Only constants, null is not okay - and it's 'fast', in that its implemented as a jump table (contrast to a sequence of if/elseif statements, which requires a comparison for each one. A jump table just jumps, using a single comparison, straight to the right line or to the end if none match.
Now the new stuff (12+): As an expression
These days switch can be an expression. As in, the switch returns a thing. However, in order to make that work, each and every 'path' throughout your switch must return something, and it cannot possibly be the case (heh) that there is a missing case
statement; after all, what should the expression resolve to then? Thus, in this 'mode', you must have exhausted all options. Interesting subcase of this is enums, where you can be 'exhaustive', and yet at runtime this can never be guaranteed; what if someone adds a value to an enum and recompiles JUST the enum and drops that in there? Now an erstwhile exhaustive (covers every option) switch no longer is:
int y = switch(textyNumber) {
case "one": yield 1;
case "two": yield 2;
default: yield 0;
};
yield
is a new approach here. It's a lot like 'return' - it 'returns' from the switch, returning that value. It is a compiler error if that default clause is missing.
The weirdness with enums means that if you're exhaustive (cover every enum value), it compiles, but if you then add an enum value, do not recompile the code with the switch, and attempt to toss a newly created enum value through, the switch throws a java.lang.IncompatibleClassChangeError
. The more you know.
arrow syntax
Because 'yield' is a tad unwieldy and it can be useful to just one-liner the desired expression, similar to how you can write e.g.:
Comparator<String> byLength = (a, b) -> a.length() - b.length();
that same arrow syntax is now legal in switches, and for consistency, it's even legal in statement-form switches:
int x = 1;
int y = switch(x) {
case 1 -> 2;
default -> 0;
};
switch (y) {
case 0 -> System.out.println("Interesting");
}
The arrow prevents fallthrough (it implies break
), and takes only one statement. But a whole block also counts as one statement:
switch (y) {
case 0 -> {
System.out.println("one");
System.out.println("two");
}
case 1 -> {
System.out.println("three");
}
}
would print just 'one', 'two'.
multi-case
Commas can be used to separate values; this is similar to how you can use bars to separate exception types (catch (IOException | SQLException e) { ... }
. Applies to all forms.
pattern matching
Now we get to the real intriguing stuff:
record Point(int x, int y) {}
...
Object p = new Point(10, 20);
switch (p) {
case Point(x, y) -> System.out.printf("Point [%d, %d]", x, y);
}
will soon be legal java! Now the thing that follows the 'case' is a 'template'. Can you smash whatever p
resolves to into this template? If yes, the case matches, and the variables are filled in for you. This works with 'deconstructable' types (currently: only records; in future any type can provide a deconstructor), as well as an instanceof kinda deal:
Object o = someExpression;
switch (o) {
case Number n -> System.out.println("is a number. intvalue: " + n.intValue());
case String s -> System.out.println("is a string: " + s.toLowerCase());
}
I think that covers all the updates that are available for switch up to java v 17 and a bit beyond it, even.
NB: You find this info in extreme detail and as early as you want it by following the appropriate openjdk dev mailing lists. This stuff would be found mostly on amber-dev, with some of the pattern matching stuff found on valhalla-dev due to being closely associated with records, and records overlaps with value types (valhalla = value types for java, amber = general language updates). At java conferences somebody will usually give a breakdown of where this stands, reddit's /r/java
tends to post it when someone from the OpenJDK team (for this stuff, you're looking at Brian Goetz generally) posts a major update on how the feature is envisioned / has been implemented. Follow Brian on twitter, that helps too. The release notes, and the umbrella JEP page associated with any new java release should also mention this stuff with links you can then follow.