diff --git a/README.md b/README.md index 8494245..8b13668 100644 --- a/README.md +++ b/README.md @@ -103,3 +103,4 @@ Modules for the Java 8 / 11 / 17 / 21 feature articles, one folder each, each wi - `java8-features/` — see its README - `java11-features/` — see its README - `java17-features/` — see its README +- `java21-features/` — see its README diff --git a/java21-features/README.md b/java21-features/README.md new file mode 100644 index 0000000..d67910f --- /dev/null +++ b/java21-features/README.md @@ -0,0 +1,13 @@ +# java21-features — Java 21 features with examples + +Companion code for the ankurm.com article **Java 21 Features with Examples: Virtual Threads, Record Patterns, Sequenced Collections**. The explanation lives in the article; this folder holds the runnable sources and the transcripts they produced. There is no `docs/` folder: the depth is inside the article itself. + +```bash +JDK21=/path/to/jdk21 ./run.sh # regenerates output/*.txt +``` + +- `src/` compiles and runs unchanged on the JDK named in each output header. +- `broken/` fails to compile on purpose; the compiler's own message is committed in `output/`. +- `output/` is real captured output, regenerated by `run.sh`. + +Tested on: OpenJDK 21.0.12.1 (the preview and incubator demos need --enable-preview / --add-modules, which run.sh passes). diff --git a/java21-features/broken/DominatedCase.java b/java21-features/broken/DominatedCase.java new file mode 100644 index 0000000..4b5b636 --- /dev/null +++ b/java21-features/broken/DominatedCase.java @@ -0,0 +1,5 @@ +/** Fails to compile on purpose: a more general pattern before a specific one makes the later case unreachable. */ +public class DominatedCase { + static String f(Object o) { return switch (o) { case CharSequence cs -> "cs"; case String s -> "s"; default -> "d"; }; } + public static void main(String[] args) { } +} diff --git a/java21-features/broken/NonExhaustive.java b/java21-features/broken/NonExhaustive.java new file mode 100644 index 0000000..29d2e02 --- /dev/null +++ b/java21-features/broken/NonExhaustive.java @@ -0,0 +1,8 @@ +/** Fails to compile on purpose: a pattern switch over a sealed type must cover every permitted subtype. */ +public class NonExhaustive { + sealed interface S permits A, B {} + record A() implements S {} + record B() implements S {} + static String f(S s) { return switch (s) { case A a -> "a"; }; } + public static void main(String[] args) { } +} diff --git a/java21-features/broken/PreviewWithoutFlag.java b/java21-features/broken/PreviewWithoutFlag.java new file mode 100644 index 0000000..3955779 --- /dev/null +++ b/java21-features/broken/PreviewWithoutFlag.java @@ -0,0 +1,4 @@ +/** Fails to compile on purpose: string templates are a preview feature in 21 and are off by default. */ +public class PreviewWithoutFlag { + public static void main(String[] args) { String n = "x"; System.out.println(STR."hi \{n}"); } +} diff --git a/java21-features/output/01-virtual-threads.txt b/java21-features/output/01-virtual-threads.txt new file mode 100644 index 0000000..e5efa4e --- /dev/null +++ b/java21-features/output/01-virtual-threads.txt @@ -0,0 +1,5 @@ +$ javac src/VirtualThreadsDemo.java && java VirtualThreadsDemo (21.0.12.1) +tasks completed : 10000 +finished in < 5s: true (10,000 x 1s sleeps) +isVirtual : true, daemon=true +platform isVirtual: false diff --git a/java21-features/output/02-record-patterns.txt b/java21-features/output/02-record-patterns.txt new file mode 100644 index 0000000..75986e0 --- /dev/null +++ b/java21-features/output/02-record-patterns.txt @@ -0,0 +1,5 @@ +$ javac src/RecordPatternsDemo.java && java RecordPatternsDemo (21.0.12.1) +line from (0,0) to (3,4) length^2=25 +box of String hi +box of Integer 7 +no match diff --git a/java21-features/output/03-pattern-switch.txt b/java21-features/output/03-pattern-switch.txt new file mode 100644 index 0000000..7e4fefb --- /dev/null +++ b/java21-features/output/03-pattern-switch.txt @@ -0,0 +1,3 @@ +$ javac src/PatternSwitchDemo.java && java PatternSwitchDemo (21.0.12.1) +big circle | circle | square 2.0 | rect 2.0x3.0 +null handled in the switch | large int | int | string of 4 | other diff --git a/java21-features/output/04-sequenced-collections.txt b/java21-features/output/04-sequenced-collections.txt new file mode 100644 index 0000000..05c4bc4 --- /dev/null +++ b/java21-features/output/04-sequenced-collections.txt @@ -0,0 +1,11 @@ +$ javac src/SequencedCollectionsDemo.java && java SequencedCollectionsDemo (21.0.12.1) +getFirst/getLast: a c +reversed view : [c, b, a] +addFirst+removeLast: [z, a, b] +LinkedHashSet reversed: [3, 2, 1] +firstEntry/lastEntry : x=1 z=3 +putFirst : {w=0, x=1, y=2, z=3} +reversed map : {z=3, y=2, x=1, w=0} +view sees add : [3, 2, 1] +immutable list : UnsupportedOperationException +HashSet : no getFirst (unordered) diff --git a/java21-features/output/05-kem.txt b/java21-features/output/05-kem.txt new file mode 100644 index 0000000..9d43685 --- /dev/null +++ b/java21-features/output/05-kem.txt @@ -0,0 +1,5 @@ +$ javac src/KemDemo.java && java KemDemo (21.0.12.1) +algorithm : DHKEM +secrets equal : true +secret length : 32 bytes +ciphertext length: 32 bytes diff --git a/java21-features/output/06-generational-zgc.txt b/java21-features/output/06-generational-zgc.txt new file mode 100644 index 0000000..4b5f457 --- /dev/null +++ b/java21-features/output/06-generational-zgc.txt @@ -0,0 +1,9 @@ +$ javac src/GcNamesDemo.java && java -XX:+UseZGC GcNamesDemo (21.0.12.1) +gc bean : ZGC Cycles +gc bean : ZGC Pauses + +$ javac src/GcNamesDemo.java && java -XX:+UseZGC -XX:+ZGenerational GcNamesDemo (21.0.12.1) +gc bean : ZGC Minor Cycles +gc bean : ZGC Minor Pauses +gc bean : ZGC Major Cycles +gc bean : ZGC Major Pauses diff --git a/java21-features/output/07-vector-api.txt b/java21-features/output/07-vector-api.txt new file mode 100644 index 0000000..6676ff9 --- /dev/null +++ b/java21-features/output/07-vector-api.txt @@ -0,0 +1,6 @@ +$ javac --add-modules jdk.incubator.vector src/VectorDemo.java && java --add-modules jdk.incubator.vector VectorDemo (21.0.12.1) +warning: using incubating module(s): jdk.incubator.vector +1 warning +WARNING: Using incubator modules: jdk.incubator.vector +vector == scalar: true +lanes >= 2 : true diff --git a/java21-features/output/08-string-templates.txt b/java21-features/output/08-string-templates.txt new file mode 100644 index 0000000..a8e7f54 --- /dev/null +++ b/java21-features/output/08-string-templates.txt @@ -0,0 +1,2 @@ +$ javac --release 21 --enable-preview src/StringTemplatesPreview.java && java --enable-preview StringTemplatesPreview (21.0.12.1) +Hello Ankur, 3 + 4 = 7 diff --git a/java21-features/output/09-unnamed-patterns.txt b/java21-features/output/09-unnamed-patterns.txt new file mode 100644 index 0000000..f30e01f --- /dev/null +++ b/java21-features/output/09-unnamed-patterns.txt @@ -0,0 +1,4 @@ +$ javac --release 21 --enable-preview src/UnnamedPreview.java && java --enable-preview UnnamedPreview (21.0.12.1) +x only : 5 +loop count : 3 +caught, name not needed diff --git a/java21-features/output/10-unnamed-main.txt b/java21-features/output/10-unnamed-main.txt new file mode 100644 index 0000000..476bf76 --- /dev/null +++ b/java21-features/output/10-unnamed-main.txt @@ -0,0 +1,2 @@ +$ java --enable-preview --source 21 src/UnnamedMainPreview.java (21.0.12.1) +no class, no static, no String[] args diff --git a/java21-features/output/11-scoped-values.txt b/java21-features/output/11-scoped-values.txt new file mode 100644 index 0000000..7adac9d --- /dev/null +++ b/java21-features/output/11-scoped-values.txt @@ -0,0 +1,3 @@ +$ javac --release 21 --enable-preview src/ScopedValuesPreview.java && java --enable-preview ScopedValuesPreview (21.0.12.1) +inside handler : user=ankur +outside : bound=false diff --git a/java21-features/output/12-structured-concurrency.txt b/java21-features/output/12-structured-concurrency.txt new file mode 100644 index 0000000..4e83aeb --- /dev/null +++ b/java21-features/output/12-structured-concurrency.txt @@ -0,0 +1,3 @@ +$ javac --release 21 --enable-preview src/StructuredConcurrencyPreview.java && java --enable-preview StructuredConcurrencyPreview (21.0.12.1) +both subtasks : ankur / 42 +failure shuts down siblings: java.lang.IllegalStateException: boom diff --git a/java21-features/output/13-ffm.txt b/java21-features/output/13-ffm.txt new file mode 100644 index 0000000..fbb5bbb --- /dev/null +++ b/java21-features/output/13-ffm.txt @@ -0,0 +1,2 @@ +$ javac --release 21 --enable-preview src/FfmPreview.java && java --enable-preview --enable-native-access=ALL-UNNAMED FfmPreview (21.0.12.1) +strlen("Hello Ankur") = 11 diff --git a/java21-features/output/14-compile-errors.txt b/java21-features/output/14-compile-errors.txt new file mode 100644 index 0000000..6e44e0f --- /dev/null +++ b/java21-features/output/14-compile-errors.txt @@ -0,0 +1,18 @@ +$ javac broken/PreviewWithoutFlag.java (21.0.12.1) +broken/PreviewWithoutFlag.java:3: error: string templates are a preview feature and are disabled by default. + public static void main(String[] args) { String n = "x"; System.out.println(STR."hi \{n}"); } + ^ + (use --enable-preview to enable string templates) +1 error + +$ javac broken/NonExhaustive.java (21.0.12.1) +broken/NonExhaustive.java:6: error: the switch expression does not cover all possible input values + static String f(S s) { return switch (s) { case A a -> "a"; }; } + ^ +1 error + +$ javac broken/DominatedCase.java (21.0.12.1) +broken/DominatedCase.java:3: error: this case label is dominated by a preceding case label + static String f(Object o) { return switch (o) { case CharSequence cs -> "cs"; case String s -> "s"; default -> "d"; }; } + ^ +1 error diff --git a/java21-features/run.sh b/java21-features/run.sh new file mode 100755 index 0000000..0afd8eb --- /dev/null +++ b/java21-features/run.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Java 21 features: every demo compiled and run on a real JDK 21; regenerates output/*.txt +# JDK21=/path/to/jdk21 ./run.sh +set -uo pipefail +unset JAVA_TOOL_OPTIONS +HERE="$(cd "$(dirname "$0")" && pwd)" +JDK21="${JDK21:-/opt/jdks/jdk21}" +OUT="$HERE/output"; mkdir -p "$OUT"; B="$(mktemp -d)"; trap 'rm -rf "$B"' EXIT +cd "$HERE" +ver() { "$1/bin/java" -version 2>&1 | sed -n 1p | sed 's/.*"\(.*\)".*/\1/'; } +# cr FILE CLASS [javac flags...] -- [java flags...] +cr() { local src="$1" cls="$2"; shift 2; local jf=() rf=(); while [ $# -gt 0 ] && [ "$1" != "--" ]; do jf+=("$1"); shift; done; [ "${1:-}" = "--" ] && shift; rf=("$@") + local o; o="$(mktemp -d -p "$B")" + echo "\$ javac ${jf[*]:-} $src && java ${rf[*]:-} $cls ($(ver "$JDK21"))" + "$JDK21/bin/javac" "${jf[@]}" -encoding UTF-8 -d "$o" "$src" 2>&1 | grep -v -e '^Note:' ; "$JDK21/bin/java" "${rf[@]}" -cp "$o" "$cls" 2>&1; } +co() { local src="$1"; shift; local o; o="$(mktemp -d -p "$B")" + echo "\$ javac $* $src ($(ver "$JDK21"))"; local r; r="$("$JDK21/bin/javac" "$@" -encoding UTF-8 -d "$o" "$src" 2>&1 | grep -v -e '^Note:')"; if [ -n "$r" ]; then echo "$r"; else echo "(compiled cleanly)"; fi; } +PV=(--release 21 --enable-preview) +cr src/VirtualThreadsDemo.java VirtualThreadsDemo > "$OUT/01-virtual-threads.txt" +cr src/RecordPatternsDemo.java RecordPatternsDemo > "$OUT/02-record-patterns.txt" +cr src/PatternSwitchDemo.java PatternSwitchDemo > "$OUT/03-pattern-switch.txt" +cr src/SequencedCollectionsDemo.java SequencedCollectionsDemo > "$OUT/04-sequenced-collections.txt" +cr src/KemDemo.java KemDemo > "$OUT/05-kem.txt" +{ cr src/GcNamesDemo.java GcNamesDemo -- -XX:+UseZGC; echo; cr src/GcNamesDemo.java GcNamesDemo -- -XX:+UseZGC -XX:+ZGenerational; } > "$OUT/06-generational-zgc.txt" +cr src/VectorDemo.java VectorDemo --add-modules jdk.incubator.vector -- --add-modules jdk.incubator.vector > "$OUT/07-vector-api.txt" +cr src/StringTemplatesPreview.java StringTemplatesPreview "${PV[@]}" -- --enable-preview > "$OUT/08-string-templates.txt" +cr src/UnnamedPreview.java UnnamedPreview "${PV[@]}" -- --enable-preview > "$OUT/09-unnamed-patterns.txt" +{ echo "\$ java --enable-preview --source 21 src/UnnamedMainPreview.java ($(ver "$JDK21"))"; "$JDK21/bin/java" --enable-preview --source 21 src/UnnamedMainPreview.java 2>&1 | grep -v '^Note:'; } > "$OUT/10-unnamed-main.txt" +cr src/ScopedValuesPreview.java ScopedValuesPreview "${PV[@]}" -- --enable-preview > "$OUT/11-scoped-values.txt" +cr src/StructuredConcurrencyPreview.java StructuredConcurrencyPreview "${PV[@]}" -- --enable-preview > "$OUT/12-structured-concurrency.txt" +cr src/FfmPreview.java FfmPreview "${PV[@]}" -- --enable-preview --enable-native-access=ALL-UNNAMED > "$OUT/13-ffm.txt" +{ co broken/PreviewWithoutFlag.java; echo; co broken/NonExhaustive.java; echo; co broken/DominatedCase.java; } > "$OUT/14-compile-errors.txt" +exit 0 diff --git a/java21-features/src/FfmPreview.java b/java21-features/src/FfmPreview.java new file mode 100644 index 0000000..2812b9a --- /dev/null +++ b/java21-features/src/FfmPreview.java @@ -0,0 +1,16 @@ +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; + +/** JEP 442 (third preview in 21): call C's strlen without JNI. Needs --enable-preview. */ +public class FfmPreview { + public static void main(String[] args) throws Throwable { + Linker linker = Linker.nativeLinker(); + MethodHandle strlen = linker.downcallHandle(linker.defaultLookup().find("strlen").orElseThrow(), + FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)); + try (Arena arena = Arena.ofConfined()) { + MemorySegment s = arena.allocateUtf8String("Hello Ankur"); + long len = (long) strlen.invokeExact(s); + System.out.println("strlen(\"Hello Ankur\") = " + len); + } + } +} diff --git a/java21-features/src/GcNamesDemo.java b/java21-features/src/GcNamesDemo.java new file mode 100644 index 0000000..cabf341 --- /dev/null +++ b/java21-features/src/GcNamesDemo.java @@ -0,0 +1,8 @@ +import java.lang.management.*; + +/** JEP 439 (final): Generational ZGC is opt-in on 21 with -XX:+ZGenerational. Prints the GC beans each mode registers. */ +public class GcNamesDemo { + public static void main(String[] args) { + for (GarbageCollectorMXBean b : ManagementFactory.getGarbageCollectorMXBeans()) System.out.println("gc bean : " + b.getName()); + } +} diff --git a/java21-features/src/KemDemo.java b/java21-features/src/KemDemo.java new file mode 100644 index 0000000..7f68941 --- /dev/null +++ b/java21-features/src/KemDemo.java @@ -0,0 +1,20 @@ +import javax.crypto.*; +import java.security.*; +import java.util.Arrays; + +/** JEP 452 (final): Key Encapsulation Mechanism API, with the DHKEM implementation that ships in 21. */ +public class KemDemo { + public static void main(String[] args) throws Exception { + KeyPairGenerator g = KeyPairGenerator.getInstance("X25519"); + KeyPair kp = g.generateKeyPair(); + KEM kem = KEM.getInstance("DHKEM"); + KEM.Encapsulator enc = kem.newEncapsulator(kp.getPublic()); + KEM.Encapsulated e = enc.encapsulate(); + SecretKey sender = e.key(); + SecretKey receiver = kem.newDecapsulator(kp.getPrivate()).decapsulate(e.encapsulation()); + System.out.println("algorithm : " + kem.getAlgorithm()); + System.out.println("secrets equal : " + Arrays.equals(sender.getEncoded(), receiver.getEncoded())); + System.out.println("secret length : " + sender.getEncoded().length + " bytes"); + System.out.println("ciphertext length: " + e.encapsulation().length + " bytes"); + } +} diff --git a/java21-features/src/PatternSwitchDemo.java b/java21-features/src/PatternSwitchDemo.java new file mode 100644 index 0000000..d1d329e --- /dev/null +++ b/java21-features/src/PatternSwitchDemo.java @@ -0,0 +1,29 @@ +/** JEP 441 (final): patterns in switch, guards with `when`, case null, and exhaustiveness over a sealed type. */ +public class PatternSwitchDemo { + 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 String size(Shape s) { + return switch (s) { // no default: the compiler checks all three cases + case Circle c when c.r() > 10 -> "big circle"; + case Circle c -> "circle"; + case Square q -> "square " + q.side(); + case Rect(double w, double h) -> "rect " + w + "x" + h; + }; + } + static String any(Object o) { + return switch (o) { + case null -> "null handled in the switch"; + case Integer i when i > 100 -> "large int"; + case Integer i -> "int"; + case String str -> "string of " + str.length(); + default -> "other"; + }; + } + public static void main(String[] args) { + System.out.println(size(new Circle(11)) + " | " + size(new Circle(1)) + " | " + size(new Square(2)) + " | " + size(new Rect(2, 3))); + System.out.println(any(null) + " | " + any(500) + " | " + any(5) + " | " + any("abcd") + " | " + any(2.0)); + } +} diff --git a/java21-features/src/RecordPatternsDemo.java b/java21-features/src/RecordPatternsDemo.java new file mode 100644 index 0000000..e9d12b2 --- /dev/null +++ b/java21-features/src/RecordPatternsDemo.java @@ -0,0 +1,20 @@ +/** JEP 440 (final): deconstruct records in instanceof and switch, nested. */ +public class RecordPatternsDemo { + record Point(int x, int y) {} + record Line(Point from, Point to) {} + record Box(T content) {} + + static String describe(Object o) { + if (o instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) + return "line from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ") length^2=" + ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + if (o instanceof Box(String s)) return "box of String " + s; + if (o instanceof Box(Integer i)) return "box of Integer " + i; + return "no match"; + } + public static void main(String[] args) { + System.out.println(describe(new Line(new Point(0, 0), new Point(3, 4)))); + System.out.println(describe(new Box<>("hi"))); + System.out.println(describe(new Box<>(7))); + System.out.println(describe(new Box<>(2.5))); + } +} diff --git a/java21-features/src/ScopedValuesPreview.java b/java21-features/src/ScopedValuesPreview.java new file mode 100644 index 0000000..9495812 --- /dev/null +++ b/java21-features/src/ScopedValuesPreview.java @@ -0,0 +1,9 @@ +/** JEP 446 (preview in 21): ScopedValue binds a value for the dynamic extent of a call. Needs --enable-preview. */ +public class ScopedValuesPreview { + static final ScopedValue USER = ScopedValue.newInstance(); + static void handler() { System.out.println("inside handler : user=" + USER.get()); } + public static void main(String[] args) { + ScopedValue.where(USER, "ankur").run(ScopedValuesPreview::handler); + System.out.println("outside : bound=" + USER.isBound()); + } +} diff --git a/java21-features/src/SequencedCollectionsDemo.java b/java21-features/src/SequencedCollectionsDemo.java new file mode 100644 index 0000000..be48448 --- /dev/null +++ b/java21-features/src/SequencedCollectionsDemo.java @@ -0,0 +1,29 @@ +import java.util.*; + +/** JEP 431 (final): SequencedCollection, SequencedSet, SequencedMap. */ +public class SequencedCollectionsDemo { + public static void main(String[] args) { + List list = new ArrayList<>(List.of("a", "b", "c")); + System.out.println("getFirst/getLast: " + list.getFirst() + " " + list.getLast()); + System.out.println("reversed view : " + list.reversed()); + list.addFirst("z"); list.removeLast(); + System.out.println("addFirst+removeLast: " + list); + + LinkedHashSet set = new LinkedHashSet<>(List.of(1, 2, 3)); + System.out.println("LinkedHashSet reversed: " + set.reversed()); + + LinkedHashMap map = new LinkedHashMap<>(); + map.put("x", 1); map.put("y", 2); map.put("z", 3); + System.out.println("firstEntry/lastEntry : " + map.firstEntry() + " " + map.lastEntry()); + map.putFirst("w", 0); + System.out.println("putFirst : " + map); + System.out.println("reversed map : " + map.reversed()); + + // reversed() is a view, not a copy + List src = new ArrayList<>(List.of("1", "2")); List rev = src.reversed(); src.add("3"); + System.out.println("view sees add : " + rev); + + try { List.of(1, 2).addFirst(0); } catch (UnsupportedOperationException e) { System.out.println("immutable list : UnsupportedOperationException"); } + try { new HashSet<>(List.of(1)).getClass().getMethod("getFirst"); } catch (NoSuchMethodException e) { System.out.println("HashSet : no getFirst (unordered)"); } + } +} diff --git a/java21-features/src/StringTemplatesPreview.java b/java21-features/src/StringTemplatesPreview.java new file mode 100644 index 0000000..a99850d --- /dev/null +++ b/java21-features/src/StringTemplatesPreview.java @@ -0,0 +1,7 @@ +/** JEP 430 (preview in 21, withdrawn in later releases). Needs --enable-preview. */ +public class StringTemplatesPreview { + public static void main(String[] args) { + String name = "Ankur"; int a = 3, b = 4; + System.out.println(STR."Hello \{name}, \{a} + \{b} = \{a + b}"); + } +} diff --git a/java21-features/src/StructuredConcurrencyPreview.java b/java21-features/src/StructuredConcurrencyPreview.java new file mode 100644 index 0000000..667ecc4 --- /dev/null +++ b/java21-features/src/StructuredConcurrencyPreview.java @@ -0,0 +1,20 @@ +import java.util.concurrent.StructuredTaskScope; + +/** JEP 453 (preview in 21): fork subtasks, join, and let the scope own their lifetime. Needs --enable-preview. */ +public class StructuredConcurrencyPreview { + public static void main(String[] args) throws Exception { + try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { + var user = scope.fork(() -> { Thread.sleep(50); return "ankur"; }); + var order = scope.fork(() -> { Thread.sleep(80); return 42; }); + scope.join().throwIfFailed(); + System.out.println("both subtasks : " + user.get() + " / " + order.get()); + } + try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { + scope.fork(() -> { throw new IllegalStateException("boom"); }); + scope.fork(() -> { Thread.sleep(5_000); return "never"; }); + scope.join().throwIfFailed(); + } catch (java.util.concurrent.ExecutionException e) { + System.out.println("failure shuts down siblings: " + e.getCause()); + } + } +} diff --git a/java21-features/src/UnnamedMainPreview.java b/java21-features/src/UnnamedMainPreview.java new file mode 100644 index 0000000..5fcde1a --- /dev/null +++ b/java21-features/src/UnnamedMainPreview.java @@ -0,0 +1,4 @@ +/** JEP 445 (preview in 21): an unnamed class with an instance main. Needs --enable-preview --source 21. */ +void main() { + System.out.println("no class, no static, no String[] args"); +} diff --git a/java21-features/src/UnnamedPreview.java b/java21-features/src/UnnamedPreview.java new file mode 100644 index 0000000..f1da4dd --- /dev/null +++ b/java21-features/src/UnnamedPreview.java @@ -0,0 +1,12 @@ +/** JEP 443 (preview in 21): unnamed patterns and variables, `_`. Needs --enable-preview. */ +public class UnnamedPreview { + record Point(int x, int y) {} + public static void main(String[] args) { + Object o = new Point(5, 9); + if (o instanceof Point(var x, _)) System.out.println("x only : " + x); + int total = 0; + for (var _ : java.util.List.of("a", "b", "c")) total++; + System.out.println("loop count : " + total); + try { Integer.parseInt("zz"); } catch (NumberFormatException _) { System.out.println("caught, name not needed"); } + } +} diff --git a/java21-features/src/VectorDemo.java b/java21-features/src/VectorDemo.java new file mode 100644 index 0000000..fdee48d --- /dev/null +++ b/java21-features/src/VectorDemo.java @@ -0,0 +1,16 @@ +import jdk.incubator.vector.*; + +/** JEP 448 (sixth incubator): needs --add-modules jdk.incubator.vector. Result must equal the scalar loop. */ +public class VectorDemo { + static final VectorSpecies S = IntVector.SPECIES_PREFERRED; + public static void main(String[] args) { + int n = 1000; int[] a = new int[n], b = new int[n], c = new int[n], d = new int[n]; + for (int i = 0; i < n; i++) { a[i] = i; b[i] = 2 * i; } + int i = 0; + for (; i < S.loopBound(n); i += S.length()) IntVector.fromArray(S, a, i).add(IntVector.fromArray(S, b, i)).intoArray(c, i); + for (; i < n; i++) c[i] = a[i] + b[i]; + for (int k = 0; k < n; k++) d[k] = a[k] + b[k]; + System.out.println("vector == scalar: " + java.util.Arrays.equals(c, d)); + System.out.println("lanes >= 2 : " + (S.length() >= 2)); + } +} diff --git a/java21-features/src/VirtualThreadsDemo.java b/java21-features/src/VirtualThreadsDemo.java new file mode 100644 index 0000000..5207f31 --- /dev/null +++ b/java21-features/src/VirtualThreadsDemo.java @@ -0,0 +1,22 @@ +import java.time.Duration; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** JEP 444 (final): 10,000 blocking tasks on virtual threads. Timing is machine dependent, so only counts are asserted. */ +public class VirtualThreadsDemo { + public static void main(String[] args) throws Exception { + AtomicInteger done = new AtomicInteger(); + long t0 = System.nanoTime(); + try (ExecutorService ex = Executors.newVirtualThreadPerTaskExecutor()) { + for (int i = 0; i < 10_000; i++) ex.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); done.incrementAndGet(); return null; }); + } + long ms = (System.nanoTime() - t0) / 1_000_000; + System.out.println("tasks completed : " + done.get()); + System.out.println("finished in < 5s: " + (ms < 5_000) + " (10,000 x 1s sleeps)"); + + Thread v = Thread.ofVirtual().name("demo-vt").start(() -> System.out.println("isVirtual : " + Thread.currentThread().isVirtual() + ", daemon=" + Thread.currentThread().isDaemon())); + v.join(); + Thread p = Thread.ofPlatform().name("demo-pt").start(() -> System.out.println("platform isVirtual: " + Thread.currentThread().isVirtual())); + p.join(); + } +}