/** * JEP 532, Primitive Types in Patterns, instanceof, and switch (fifth preview). * *

Until now patterns worked on reference types only. With this preview a pattern can name a * primitive type, and the test is "can this value be converted to that type WITHOUT losing * information?" Explained in docs/05-primitive-patterns.md. Compile and run with --enable-preview. */ public class PrimitivePatterns { record Reading(int celsius) {} public static void main(String[] args) { exactness(); switchOnLong(); switchOnObject(); recordPattern(); } /** instanceof with a primitive type asks: is this an EXACT conversion? */ static void exactness() { System.out.println("--- instanceof : an exact-conversion test, not a cast"); int small = 100, big = 300; boolean a = small instanceof byte; boolean b = big instanceof byte; System.out.println("100 instanceof byte = " + a); System.out.println("300 instanceof byte = " + b + " (a byte holds -128..127)"); Check.that(a && !b, "100 converts to byte exactly, 300 does not"); if (small instanceof byte narrow) { System.out.println("binding: byte narrow = " + narrow); } int precise = 16_777_217; // 2^24 + 1: the first int a float cannot hold boolean f1 = 16_777_216 instanceof float; boolean f2 = precise instanceof float; System.out.println("16_777_216 instanceof float = " + f1); System.out.println("16_777_217 instanceof float = " + f2 + " (float has a 24-bit significand)"); Check.that(f1 && !f2, "int to float is exact up to 2^24 and lossy after"); } /** switch over a long, with a primitive pattern and a guard. */ static String classify(long value) { return switch (value) { case 0L -> "zero"; case 1L -> "one"; case long v when v < 0 -> "negative (" + v + ")"; case long v -> "positive (" + v + ")"; }; } static void switchOnLong() { System.out.println("--- switch on a long, with pattern labels and a guard"); for (long v : new long[] {0, 1, -7, 9_000_000_000L}) { System.out.println("classify(" + v + ") = " + classify(v)); } Check.that(classify(0).equals("zero") && classify(-7).startsWith("negative"), "long switch dispatches on constants, then patterns"); } /** switch over an Object: primitive patterns match the boxed types they correspond to. */ static String describe(Object o) { return switch (o) { case int i when i > 1000 -> "a large int " + i; case int i -> "an int " + i; case long l -> "a long " + l; case double d -> "a double " + d; case String s -> "a String of length " + s.length(); default -> "something else: " + o; }; } static void switchOnObject() { System.out.println("--- switch on an Object: an Integer matches 'case int'"); Object[] samples = {7, 5000, 7L, 2.5, "hello", 'x'}; for (Object o : samples) { System.out.println("describe(" + o.getClass().getSimpleName() + " " + o + ") = " + describe(o)); } Check.that(describe(5000).equals("a large int 5000"), "an Integer 5000 matches case int with a guard"); } /** Record patterns can now narrow a component: Reading.celsius is an int, the pattern asks for a byte. */ static void recordPattern() { System.out.println("--- record pattern with a narrowing primitive component"); Object[] readings = {new Reading(36), new Reading(4_000)}; for (Object o : readings) { String verdict = switch (o) { case Reading(byte c) -> "fits in a byte: " + c; case Reading(int c) -> "needs a full int: " + c; default -> "not a reading"; }; System.out.println(o + " -> " + verdict); } Check.that(readings[0] instanceof Reading(byte _) && !(readings[1] instanceof Reading(byte _)), "Reading(byte c) matches 36 but not 4000"); } }