Add patterns module: pattern matching from JDK 21 to 27

Type, record and primitive patterns, guards, sealed exhaustiveness, MatchException via separate compilation, and eight captured transcripts.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TF9JWFvJSNm6HVzswzZU5a
This commit is contained in:
Claude
2026-09-24 11:50:40 +00:00
parent 96b25c8fc4
commit bc91b9c8d0
36 changed files with 736 additions and 0 deletions
+1
View File
@@ -65,6 +65,7 @@ These modules keep only sources, a `run.sh` and their transcripts (`output/`). T
| [`jep512/`](jep512) | Compact Source Files and Instance Main Methods in Java 25 (JEP 512) | `java Hello.java` on JDK 21 vs 25 vs 27, the class `javac` builds around `void main()`, implicit `java.base` imports, `java.lang.IO`, the measured launch order, a private-constructor difference between 25 and 27 |
| [`jep513/`](jep513) | Flexible Constructor Bodies in Java 25 (JEP 513): Validate Before super() | statements before `super(...)`/`this(...)`, the parent-calls-child bug and its fix, twelve compile errors that remain with the 25 and 27 wordings side by side, `--release` rejection |
| [`jep511/`](jep511) | Module Import Declarations in Java 25 (JEP 511) | `import module java.base;` against seven single imports, ambiguity errors and how single-type, on-demand and same-package names beat a module import, what one module import does and does not bring in (`java.sql` does not give `java.base`; `java.se` needs `--add-modules`), named modules, JEP 512 compact files, jshell's default imports, identical bytecode |
| [`patterns/`](patterns) | Pattern Matching in Java: switch, Record Patterns and Primitive Patterns (JDK 21 to 27) | type and record patterns, guards and dominance errors, sealed exhaustiveness (with JDK 27's new `missing patterns` lines), `MatchException` from a throwing accessor and from separate compilation, unnamed patterns, primitive patterns with `--enable-preview` and the preview class-file trap |
## Captured output (`docs/output/`)
+15
View File
@@ -0,0 +1,15 @@
# patterns — Pattern matching from JDK 21 to 27
Companion code for the ankurm.com article **Pattern Matching in Java: switch, Record Patterns and Primitive Patterns (JDK 21 to 27)**. The explanation
lives in the article; this folder holds the runnable sources and the transcripts they produced.
```bash
JDK25=/path/to/jdk-25 JDK27=/path/to/jdk-27 [JDK21=/path/to/jdk-21] ./run.sh # regenerates output/*.txt
```
- `src/` compiles and runs on 21, 25 and 27 (except `Unnamed.java`, which needs 22 or later).
- `broken/` fails to compile on purpose.
- `preview/` uses primitive patterns (JEP 532, preview in 27) and needs `--enable-preview --release N`.
- `sep/` is a sealed hierarchy compiled in two versions, to show the one runtime failure an exhaustive `switch` cannot prevent.
Tested on Temurin 25.0.4.1+1 and 27+35, and OpenJDK 21.0.10 for the baseline.
+10
View File
@@ -0,0 +1,10 @@
public class Dominated {
static String f(Object o) {
return switch (o) {
case CharSequence cs -> "chars";
case String s -> "string"; // String is a CharSequence, so this can never be reached
default -> "other";
};
}
public static void main(String[] args) { System.out.println(f("x")); }
}
+10
View File
@@ -0,0 +1,10 @@
public class GuardBeforeGeneral {
static String f(Object o) {
return switch (o) {
case String s -> "any string";
case String s when s.isEmpty() -> "empty string"; // guarded case after the unguarded one
default -> "other";
};
}
public static void main(String[] args) { System.out.println(f("")); }
}
+15
View File
@@ -0,0 +1,15 @@
public class NotExhaustive {
sealed interface Shape permits Circle, Square, Rect {}
record Circle(double r) implements Shape {}
record Square(double side) implements Shape {}
record Rect(double w, double h) implements Shape {}
static double area(Shape s) {
return switch (s) {
case Circle(double r) -> Math.PI * r * r;
case Square(double side) -> side * side;
// Rect is missing
};
}
public static void main(String[] args) { System.out.println(area(new Circle(1))); }
}
+9
View File
@@ -0,0 +1,9 @@
public class NotExhaustiveObject {
static String f(Object o) {
return switch (o) {
case String s -> "string";
case Integer i -> "int";
};
}
public static void main(String[] args) { System.out.println(f("x")); }
}
+23
View File
@@ -0,0 +1,23 @@
$ javac src/TypePatterns.java && java TypePatterns (21.0.10+7-Ubuntu-124.04)
string of length 5 / string of length 5
3 -1
int 42
string JAVA
int array of 3
other Double
$ javac src/TypePatterns.java && java TypePatterns (25.0.4.1+1-LTS)
string of length 5 / string of length 5
3 -1
int 42
string JAVA
int array of 3
other Double
$ javac src/TypePatterns.java && java TypePatterns (27+35)
string of length 5 / string of length 5
3 -1
int 42
string JAVA
int array of 3
other Double
@@ -0,0 +1,39 @@
$ javac src/Guards.java && java Guards (25.0.4.1+1-LTS)
empty string
short string: abc
long string: abcdefgh
negative -4
int 12
other
$ javac src/Guards.java && java Guards (27+35)
empty string
short string: abc
long string: abcdefgh
negative -4
int 12
other
$ javac broken/Dominated.java (25.0.4.1+1-LTS)
broken/Dominated.java:5: error: this case label is dominated by a preceding case label
case String s -> "string"; // String is a CharSequence, so this can never be reached
^
1 error
$ javac broken/Dominated.java (27+35)
broken/Dominated.java:5: error: this case label is dominated by a preceding case label
case String s -> "string"; // String is a CharSequence, so this can never be reached
^
1 error
$ javac broken/GuardBeforeGeneral.java (25.0.4.1+1-LTS)
broken/GuardBeforeGeneral.java:5: error: this case label is dominated by a preceding case label
case String s when s.isEmpty() -> "empty string"; // guarded case after the unguarded one
^
1 error
$ javac broken/GuardBeforeGeneral.java (27+35)
broken/GuardBeforeGeneral.java:5: error: this case label is dominated by a preceding case label
case String s when s.isEmpty() -> "empty string"; // guarded case after the unguarded one
^
1 error
+31
View File
@@ -0,0 +1,31 @@
$ javac src/RecordPatterns.java && java RecordPatterns (21.0.10+7-Ubuntu-124.04)
point at 3,4
not a point
7
box of string hi
box of int 7
box of 2.5
$ javac src/RecordPatterns.java && java RecordPatterns (25.0.4.1+1-LTS)
point at 3,4
not a point
7
box of string hi
box of int 7
box of 2.5
$ javac src/RecordPatterns.java && java RecordPatterns (27+35)
point at 3,4
not a point
7
box of string hi
box of int 7
box of 2.5
$ javac src/MatchExceptionDemo.java && java MatchExceptionDemo (25.0.4.1+1-LTS)
MatchException: java.lang.IllegalStateException: accessor blew up
cause: java.lang.IllegalStateException: accessor blew up
$ javac src/MatchExceptionDemo.java && java MatchExceptionDemo (27+35)
MatchException: java.lang.IllegalStateException: accessor blew up
cause: java.lang.IllegalStateException: accessor blew up
+54
View File
@@ -0,0 +1,54 @@
$ javac src/SealedExhaustive.java && java SealedExhaustive (21.0.10+7-Ubuntu-124.04)
Circle[r=1.0] -> 3.14
Square[side=2.0] -> 4.00
Rect[w=2.0, h=3.0] -> 6.00
$ javac src/SealedExhaustive.java && java SealedExhaustive (25.0.4.1+1-LTS)
Circle[r=1.0] -> 3.14
Square[side=2.0] -> 4.00
Rect[w=2.0, h=3.0] -> 6.00
$ javac src/SealedExhaustive.java && java SealedExhaustive (27+35)
Circle[r=1.0] -> 3.14
Square[side=2.0] -> 4.00
Rect[w=2.0, h=3.0] -> 6.00
$ javac broken/NotExhaustive.java (21.0.10+7-Ubuntu-124.04)
broken/NotExhaustive.java:8: error: the switch expression does not cover all possible input values
return switch (s) {
^
1 error
$ javac broken/NotExhaustive.java (25.0.4.1+1-LTS)
broken/NotExhaustive.java:8: error: the switch expression does not cover all possible input values
return switch (s) {
^
1 error
$ javac broken/NotExhaustive.java (27+35)
broken/NotExhaustive.java:8: error: the switch expression does not cover all possible input values
return switch (s) {
^
missing patterns:
Rect _
1 error
$ javac broken/NotExhaustiveObject.java (21.0.10+7-Ubuntu-124.04)
broken/NotExhaustiveObject.java:3: error: the switch expression does not cover all possible input values
return switch (o) {
^
1 error
$ javac broken/NotExhaustiveObject.java (25.0.4.1+1-LTS)
broken/NotExhaustiveObject.java:3: error: the switch expression does not cover all possible input values
return switch (o) {
^
1 error
$ javac broken/NotExhaustiveObject.java (27+35)
broken/NotExhaustiveObject.java:3: error: the switch expression does not cover all possible input values
return switch (o) {
^
missing patterns:
Object _
1 error
@@ -0,0 +1,32 @@
$ java -cp old Client (Client and Shape compiled together, v1) (25.0.4.1+1-LTS)
circle: 3.141592653589793
square: 4.0
$ java -cp new:old Main (Shape gained Triangle; Client.class NOT recompiled) (25.0.4.1+1-LTS)
Exception in thread "main" java.lang.MatchException
at Client.area(Client.java:4)
at Main.main(Main.java:4)
$ javac -cp new sep/client/Client.java (now recompile Client against v2) (25.0.4.1+1-LTS)
sep/client/Client.java:4: error: the switch expression does not cover all possible input values
return switch (s) {
^
1 error
$ java -cp old Client (Client and Shape compiled together, v1) (27+35)
circle: 3.141592653589793
square: 4.0
$ java -cp new:old Main (Shape gained Triangle; Client.class NOT recompiled) (27+35)
Exception in thread "main" java.lang.MatchException
at Client.area(Client.java:4)
at Main.main(Main.java:4)
$ javac -cp new sep/client/Client.java (now recompile Client against v2) (27+35)
sep/client/Client.java:4: error: the switch expression does not cover all possible input values
return switch (s) {
^
missing patterns:
Triangle _
1 error
+26
View File
@@ -0,0 +1,26 @@
$ javac src/NullCase.java && java NullCase (25.0.4.1+1-LTS)
without case null: NullPointerException
with case null: null!
case null, default: null or something else
instanceof with null: false
$ javac src/NullCase.java && java NullCase (27+35)
without case null: NullPointerException
with case null: null!
case null, default: null or something else
instanceof with null: false
$ javac src/Unnamed.java && java Unnamed (21.0.10+7-Ubuntu-124.04)
src/Unnamed.java:9: error: unnamed variables are a preview feature and are disabled by default.
case Circle _ -> "round"; // the type matters, the value does not
^
(use --enable-preview to enable unnamed variables)
1 error
$ javac src/Unnamed.java && java Unnamed (25.0.4.1+1-LTS)
round angular
5
$ javac src/Unnamed.java && java Unnamed (27+35)
round angular
5
+87
View File
@@ -0,0 +1,87 @@
$ javac preview/PrimitiveInstanceof.java (25.0.4.1+1-LTS)
preview/PrimitiveInstanceof.java:3: error: primitive patterns are a preview feature and are disabled by default.
if (i instanceof byte b) return i + " fits in a byte: " + b;
^
(use --enable-preview to enable primitive patterns)
1 error
$ javac preview/PrimitiveInstanceof.java (27+35)
preview/PrimitiveInstanceof.java:3: error: primitive patterns are a preview feature and are disabled by default.
if (i instanceof byte b) return i + " fits in a byte: " + b;
^
(use --enable-preview to enable primitive patterns)
1 error
$ javac --enable-preview --release 25 preview/PrimitiveInstanceof.java && java --enable-preview PrimitiveInstanceof (25.0.4.1+1-LTS)
100 fits in a byte: 100
1000 fits in a short: 1000
100000 needs an int
0.5 converts to float 0.5 without loss
0.1 does NOT convert to float without loss
$ javac --enable-preview --release 27 preview/PrimitiveInstanceof.java && java --enable-preview PrimitiveInstanceof (27+35)
100 fits in a byte: 100
1000 fits in a short: 1000
100000 needs an int
0.5 converts to float 0.5 without loss
0.1 does NOT convert to float without loss
$ javac --enable-preview --release 25 preview/PrimitiveSwitch.java && java --enable-preview PrimitiveSwitch (25.0.4.1+1-LTS)
OK | other success 204 | client error 404 | something else 500
yes no
zero one many (9223372036854775807)
$ javac --enable-preview --release 27 preview/PrimitiveSwitch.java && java --enable-preview PrimitiveSwitch (27+35)
OK | other success 204 | client error 404 | something else 500
yes no
zero one many (9223372036854775807)
$ javac --enable-preview --release 25 preview/PrimitiveInRecord.java && java --enable-preview PrimitiveInRecord (25.0.4.1+1-LTS)
whole degrees: 21
fractional: 21.5
not a reading
$ javac --enable-preview --release 27 preview/PrimitiveInRecord.java && java --enable-preview PrimitiveInRecord (27+35)
whole degrees: 21
fractional: 21.5
not a reading
$ javac --enable-preview --release 25 preview/PrimitiveEdges.java && java --enable-preview PrimitiveEdges (25.0.4.1+1-LTS)
0.0 int? true float? true
-0.0 int? false float? true
NaN int? false float? true
1.6777217E7 int? true float? false
1.0E10 int? false float? true
3.0 int? true float? true
1099511627776 instanceof int? false
Object holding Integer matches int pattern: 42
Short in Object does not match int
65 as char: A
$ javac --enable-preview --release 27 preview/PrimitiveEdges.java && java --enable-preview PrimitiveEdges (27+35)
0.0 int? true float? true
-0.0 int? false float? true
NaN int? false float? true
1.6777217E7 int? true float? false
1.0E10 int? false float? true
3.0 int? true float? true
1099511627776 instanceof int? false
Object holding Integer matches int pattern: 42
Short in Object does not match int
65 as char: A
$ javac --enable-preview --release 25 preview/BoxedToNarrower.java && java --enable-preview BoxedToNarrower (25.0.4.1+1-LTS)
preview/BoxedToNarrower.java:4: error: incompatible types: Integer cannot be converted to byte
if (boxed instanceof byte b) System.out.println("fits a byte: " + b); // Integer to byte is not allowed
^
1 error
Error: Could not find or load main class BoxedToNarrower
Caused by: java.lang.ClassNotFoundException: BoxedToNarrower
$ javac --enable-preview --release 27 preview/BoxedToNarrower.java && java --enable-preview BoxedToNarrower (27+35)
preview/BoxedToNarrower.java:4: error: incompatible types: Integer cannot be converted to byte
if (boxed instanceof byte b) System.out.println("fits a byte: " + b); // Integer to byte is not allowed
^
1 error
Error: Could not find or load main class BoxedToNarrower
Caused by: java.lang.ClassNotFoundException: BoxedToNarrower
@@ -0,0 +1,11 @@
$ javap -v PrimitiveSwitch | grep -E 'minor|major' (compiled by 25.0.4.1+1-LTS with --enable-preview)
minor version: 65535
major version: 69
$ java --enable-preview PrimitiveSwitch (class compiled on 25, run on 27+35)
Error: LinkageError occurred while loading main class PrimitiveSwitch
java.lang.UnsupportedClassVersionError: PrimitiveSwitch (class file version 69.65535) was compiled with preview features that are unsupported. This version of the Java Runtime only recognizes preview features for class file version 71.65535
$ java PrimitiveSwitch (same class, no flag, run on 25.0.4.1+1-LTS)
Error: LinkageError occurred while loading main class PrimitiveSwitch
java.lang.UnsupportedClassVersionError: Preview features are not enabled for PrimitiveSwitch (class file version 69.65535). Try running with '--enable-preview'
+6
View File
@@ -0,0 +1,6 @@
public class BoxedToNarrower {
public static void main(String[] args) {
Integer boxed = 300;
if (boxed instanceof byte b) System.out.println("fits a byte: " + b); // Integer to byte is not allowed
}
}
+16
View File
@@ -0,0 +1,16 @@
public class PrimitiveEdges {
public static void main(String[] args) {
double[] ds = {0.0, -0.0, Double.NaN, 16777217.0, 1e10, 3.0};
for (double d : ds) {
System.out.printf("%-12s int? %-5b float? %-5b%n", d, d instanceof int, d instanceof float);
}
long big = 1L << 40;
System.out.println(big + " instanceof int? " + (big instanceof int));
Object o = 42;
if (o instanceof int i) System.out.println("Object holding Integer matches int pattern: " + i);
Object s = (short) 7;
if (s instanceof int i) System.out.println("Short in Object matches int? " + i); else System.out.println("Short in Object does not match int");
int x = 65;
if (x instanceof char c) System.out.println("65 as char: " + c);
}
}
+17
View File
@@ -0,0 +1,17 @@
public class PrimitiveInRecord {
record Reading(double celsius) {}
static String classify(Object o) {
return switch (o) {
case Reading(int whole) -> "whole degrees: " + whole; // double component matched against an int pattern
case Reading(double d) -> "fractional: " + d;
default -> "not a reading";
};
}
public static void main(String[] args) {
System.out.println(classify(new Reading(21.0)));
System.out.println(classify(new Reading(21.5)));
System.out.println(classify("x"));
}
}
+20
View File
@@ -0,0 +1,20 @@
public class PrimitiveInstanceof {
static String describe(int i) {
if (i instanceof byte b) return i + " fits in a byte: " + b;
if (i instanceof short s) return i + " fits in a short: " + s;
return i + " needs an int";
}
static void floats(double d) {
if (d instanceof float f) System.out.println(d + " converts to float " + f + " without loss");
else System.out.println(d + " does NOT convert to float without loss");
}
public static void main(String[] args) {
System.out.println(describe(100));
System.out.println(describe(1000));
System.out.println(describe(100000));
floats(0.5);
floats(0.1);
}
}
+31
View File
@@ -0,0 +1,31 @@
public class PrimitiveSwitch {
static String httpFamily(int status) {
return switch (status) {
case 200 -> "OK";
case int i when i >= 200 && i < 300 -> "other success " + i;
case int i when i >= 400 && i < 500 -> "client error " + i;
case int i -> "something else " + i;
};
}
static String flag(boolean b) {
return switch (b) {
case true -> "yes";
case false -> "no";
};
}
static String big(long n) {
return switch (n) {
case 0L -> "zero";
case 1L -> "one";
case long l -> "many (" + l + ")";
};
}
public static void main(String[] args) {
System.out.println(httpFamily(200) + " | " + httpFamily(204) + " | " + httpFamily(404) + " | " + httpFamily(500));
System.out.println(flag(true) + " " + flag(false));
System.out.println(big(0) + " " + big(1) + " " + big(Long.MAX_VALUE));
}
}
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Pattern matching from JDK 21 to 27: type/record patterns, guards, exhaustiveness, unnamed patterns (final),
# and primitive patterns (JEP 532, preview in 27; also preview on 25 with --enable-preview).
# Regenerates every file in output/. JDK25=/path JDK27=/path [JDK21=/path] ./run.sh
set -uo pipefail
unset JAVA_TOOL_OPTIONS
HERE="$(cd "$(dirname "$0")" && pwd)"
JDK25="${JDK25:-/opt/jdks/jdk-25.0.4.1+1}"; JDK27="${JDK27:-/opt/jdks/jdk27}"; JDK21="${JDK21:-}"
OUT="$HERE/output"; mkdir -p "$OUT"; B="$(mktemp -d)"; trap 'rm -rf "$B"' EXIT
cd "$HERE"
t() { "$1/bin/java" -version 2>&1 | sed -n 2p | sed 's/.*(build \(.*\))/\1/'; }
rel() { case "$1" in "$JDK25") echo 25;; "$JDK27") echo 27;; *) echo 21;; esac; }
mk() { mktemp -d -p "$B"; }
# compile + run (no flags)
cr() { local jdk="$1" src="$2" cls="$3" o; o="$(mk)"
echo "\$ javac $src && java $cls ($(t "$jdk"))"
"$jdk/bin/javac" -d "$o" "$src" 2>&1 && "$jdk/bin/java" -cp "$o" "$cls" 2>&1; }
# compile only, show diagnostics
co() { local jdk="$1" src="$2" o; o="$(mk)"
echo "\$ javac $src ($(t "$jdk"))"
"$jdk/bin/javac" -d "$o" "$src" 2>&1 | grep -v '^Note:'; }
# preview compile + run
crp() { local jdk="$1" src="$2" cls="$3" o r; o="$(mk)"; r="$(rel "$jdk")"
echo "\$ javac --enable-preview --release $r $src && java --enable-preview $cls ($(t "$jdk"))"
"$jdk/bin/javac" --enable-preview --release "$r" -d "$o" "$src" 2>&1 | grep -v '^Note:'
"$jdk/bin/java" --enable-preview -cp "$o" "$cls" 2>&1; }
both() { local fn="$1"; shift; "$fn" "$JDK25" "$@"; echo; "$fn" "$JDK27" "$@"; }
all3() { local fn="$1"; shift; [ -n "$JDK21" ] && { "$fn" "$JDK21" "$@"; echo; }; "$fn" "$JDK25" "$@"; echo; "$fn" "$JDK27" "$@"; }
# 01 - type patterns
{ all3 cr src/TypePatterns.java TypePatterns; } > "$OUT/01-type-patterns.txt"
# 02 - guards and dominance
{ both cr src/Guards.java Guards; echo; both co broken/Dominated.java; echo; both co broken/GuardBeforeGeneral.java; } > "$OUT/02-guards-and-dominance.txt"
# 03 - record patterns, and what happens when an accessor throws
{ all3 cr src/RecordPatterns.java RecordPatterns; echo; both cr src/MatchExceptionDemo.java MatchExceptionDemo; } > "$OUT/03-record-patterns.txt"
# 04 - exhaustiveness: the compiler's check, and what separate compilation can still break
{ all3 cr src/SealedExhaustive.java SealedExhaustive; echo
all3 co broken/NotExhaustive.java; echo
all3 co broken/NotExhaustiveObject.java; } > "$OUT/04-exhaustiveness.txt"
{ for j in "$JDK25" "$JDK27"; do
o="$(mk)"; mkdir "$o/old" "$o/new"
"$j/bin/javac" -d "$o/old" sep/v1/*.java sep/client/Client.java 2>&1
"$j/bin/javac" -cp "$o/old" -d "$o/new" sep/v2/*.java 2>&1
echo "\$ java -cp old Client (Client and Shape compiled together, v1) ($(t "$j"))"; "$j/bin/java" -cp "$o/old" Client 2>&1; echo
echo "\$ java -cp new:old Main (Shape gained Triangle; Client.class NOT recompiled) ($(t "$j"))"; "$j/bin/java" -cp "$o/new:$o/old" Main 2>&1 | head -4; echo
echo "\$ javac -cp new sep/client/Client.java (now recompile Client against v2) ($(t "$j"))"
"$j/bin/javac" -cp "$o/new" -d "$(mk)" sep/client/Client.java 2>&1; echo
done; } > "$OUT/05-separate-compilation.txt"
# 06 - null and unnamed patterns
{ both cr src/NullCase.java NullCase; echo; all3 cr src/Unnamed.java Unnamed; } > "$OUT/06-null-and-unnamed.txt"
# 07 - primitive patterns (preview)
{ for f in PrimitiveInstanceof; do both co preview/$f.java; done; echo
for f in PrimitiveInstanceof PrimitiveSwitch PrimitiveInRecord PrimitiveEdges; do both crp preview/$f.java $f; echo; done
both crp preview/BoxedToNarrower.java BoxedToNarrower; } > "$OUT/07-primitive-patterns.txt"
{ o="$(mk)"; "$JDK25/bin/javac" --enable-preview --release 25 -d "$o" preview/PrimitiveSwitch.java 2>&1 | grep -v '^Note:'
echo "\$ javap -v PrimitiveSwitch | grep -E 'minor|major' (compiled by $(t "$JDK25") with --enable-preview)"
"$JDK25/bin/javap" -v -cp "$o" PrimitiveSwitch | grep -E 'minor|major'; echo
echo "\$ java --enable-preview PrimitiveSwitch (class compiled on 25, run on $(t "$JDK27"))"; "$JDK27/bin/java" --enable-preview -cp "$o" PrimitiveSwitch 2>&1 | head -2; echo
echo "\$ java PrimitiveSwitch (same class, no flag, run on $(t "$JDK25"))"; "$JDK25/bin/java" -cp "$o" PrimitiveSwitch 2>&1 | head -2
} > "$OUT/08-preview-class-files.txt"
echo "wrote $OUT"
+14
View File
@@ -0,0 +1,14 @@
public class Client {
// Compiled against Shape v1, where the only permitted subtypes are Circle and Square.
public static double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.r() * c.r();
case Square q -> q.side() * q.side();
};
}
public static void main(String[] args) {
System.out.println("circle: " + area(new Circle(1)));
System.out.println("square: " + area(new Square(2)));
}
}
+1
View File
@@ -0,0 +1 @@
public record Circle(double r) implements Shape {}
+1
View File
@@ -0,0 +1 @@
public sealed interface Shape permits Circle, Square {}
+1
View File
@@ -0,0 +1 @@
public record Square(double side) implements Shape {}
+1
View File
@@ -0,0 +1 @@
public record Circle(double r) implements Shape {}
+6
View File
@@ -0,0 +1,6 @@
public class Main {
public static void main(String[] args) {
// Triangle exists only in v2. Client was compiled before it did.
System.out.println("triangle: " + Client.area(new Triangle(3, 4)));
}
}
+1
View File
@@ -0,0 +1 @@
public sealed interface Shape permits Circle, Square, Triangle {}
+1
View File
@@ -0,0 +1 @@
public record Square(double side) implements Shape {}
+1
View File
@@ -0,0 +1 @@
public record Triangle(double base, double height) implements Shape {}
+16
View File
@@ -0,0 +1,16 @@
public class Guards {
static String size(Object o) {
return switch (o) {
case String s when s.isEmpty() -> "empty string";
case String s when s.length() < 5 -> "short string: " + s;
case String s -> "long string: " + s;
case Integer i when i < 0 -> "negative " + i;
case Integer i -> "int " + i;
default -> "other";
};
}
public static void main(String[] args) {
for (Object o : new Object[]{"", "abc", "abcdefgh", -4, 12, 1.5}) System.out.println(size(o));
}
}
+15
View File
@@ -0,0 +1,15 @@
public class MatchExceptionDemo {
record Fragile(int v) {
public int v() { throw new IllegalStateException("accessor blew up"); }
}
public static void main(String[] args) {
Object o = new Fragile(1);
try {
if (o instanceof Fragile(int v)) System.out.println("matched " + v);
} catch (MatchException e) {
System.out.println("MatchException: " + e.getMessage());
System.out.println("cause: " + e.getCause());
}
}
}
+31
View File
@@ -0,0 +1,31 @@
public class NullCase {
static String withoutCaseNull(Object o) {
return switch (o) {
case String s -> "string";
default -> "other";
};
}
static String withCaseNull(Object o) {
return switch (o) {
case null -> "null!";
case String s -> "string";
default -> "other";
};
}
static String combined(Object o) {
return switch (o) {
case String s -> "string";
case null, default -> "null or something else";
};
}
public static void main(String[] args) {
try { System.out.println(withoutCaseNull(null)); }
catch (NullPointerException e) { System.out.println("without case null: NullPointerException"); }
System.out.println("with case null: " + withCaseNull(null));
System.out.println("case null, default: " + combined(null));
System.out.println("instanceof with null: " + (null instanceof String));
}
}
+39
View File
@@ -0,0 +1,39 @@
public class RecordPatterns {
record Point(int x, int y) {}
record Line(Point from, Point to) {}
record Box<T>(T content) {}
// Deconstruct a record: the components are bound to x and y.
static String where(Object o) {
if (o instanceof Point(int x, int y)) {
return "point at " + x + "," + y;
}
return "not a point";
}
// Patterns nest: take a Line apart into its four coordinates in one step.
static int length1(Line line) {
if (line instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
return Math.abs(x2 - x1) + Math.abs(y2 - y1);
}
return -1;
}
// A nested pattern can also test the component's type.
static String box(Box<Object> b) {
return switch (b) {
case Box<Object>(String s) -> "box of string " + s;
case Box<Object>(Integer i) -> "box of int " + i;
case Box<Object>(var other) -> "box of " + other;
};
}
public static void main(String[] args) {
System.out.println(where(new Point(3, 4)));
System.out.println(where("nope"));
System.out.println(length1(new Line(new Point(0, 0), new Point(3, 4))));
System.out.println(box(new Box<>("hi")));
System.out.println(box(new Box<>(7)));
System.out.println(box(new Box<>(2.5)));
}
}
+21
View File
@@ -0,0 +1,21 @@
public class SealedExhaustive {
sealed interface Shape permits Circle, Square, Rect {}
record Circle(double r) implements Shape {}
record Square(double side) implements Shape {}
record Rect(double w, double h) implements Shape {}
// No default: the compiler checks that every permitted subtype is covered.
static double area(Shape s) {
return switch (s) {
case Circle(double r) -> Math.PI * r * r;
case Square(double side) -> side * side;
case Rect(double w, double h) -> w * h;
};
}
public static void main(String[] args) {
for (Shape s : new Shape[]{new Circle(1), new Square(2), new Rect(2, 3)}) {
System.out.printf("%s -> %.2f%n", s, area(s));
}
}
}
+45
View File
@@ -0,0 +1,45 @@
public class TypePatterns {
// The old way: test, cast, then use.
static String oldStyle(Object o) {
if (o instanceof String) {
String s = (String) o;
return "string of length " + s.length();
}
return "something else";
}
// The pattern way: test and bind in one step.
static String newStyle(Object o) {
if (o instanceof String s) {
return "string of length " + s.length();
}
return "something else";
}
// The binding variable stays in scope wherever the test is known to have succeeded.
static int lengthOrMinusOne(Object o) {
if (!(o instanceof String s)) {
return -1;
}
return s.length(); // s is in scope here: we only get here if the test succeeded
}
// A switch can test many types in a row.
static String describe(Object o) {
return switch (o) {
case Integer i -> "int " + (i + 1);
case String s -> "string " + s.toUpperCase();
case int[] arr -> "int array of " + arr.length;
default -> "other " + o.getClass().getSimpleName();
};
}
public static void main(String[] args) {
System.out.println(oldStyle("hello") + " / " + newStyle("hello"));
System.out.println(lengthOrMinusOne("abc") + " " + lengthOrMinusOne(42));
System.out.println(describe(41));
System.out.println(describe("java"));
System.out.println(describe(new int[]{1, 2, 3}));
System.out.println(describe(3.5));
}
}
+23
View File
@@ -0,0 +1,23 @@
public class Unnamed {
record Point(int x, int y) {}
sealed interface Shape permits Circle, Square {}
record Circle(double r) implements Shape {}
record Square(double side) implements Shape {}
static String kind(Shape s) {
return switch (s) {
case Circle _ -> "round"; // the type matters, the value does not
case Square _ -> "angular";
};
}
static int xOnly(Object o) {
if (o instanceof Point(int x, _)) return x; // do not care about y
return -1;
}
public static void main(String[] args) {
System.out.println(kind(new Circle(1)) + " " + kind(new Square(1)));
System.out.println(xOnly(new Point(5, 9)));
}
}