Upgrade to Pro — share decks privately, control downloads, hide ads and more …

State of pattern matching in Java

State of pattern matching in Java

Slides from my Foojay JUG tour talk

Let's have a quick rundown of the two most exciting features landing in OpenJDK 17!

Pattern matching in Java is almost complete now. OpenJDK 16 added support for pattern matching in the instanceof operator and now this can be used in switch cases to perform idiomatic pattern matching, as in many other modern languages.

Let's see all the possibilities with it first and then we will look at the new Sealed classes and interfaces that let you restrict inheritance and learn how they can be useful in pattern matching.

Deepu K Sasidharan

September 28, 2021
Tweet

More Decks by Deepu K Sasidharan

Other Decks in Programming

Transcript

  1. Deepu K Sasidharan JHipster co-lead Creator of KDash Developer Advocate

    at Okta OSS aficionado, author, speaker, polyglot dev @deepu105 deepu.tech deepu105
  2. Pattern matching? “The act of checking a given sequence of

    tokens for the presence of the constituents of some pattern”
  3. Why Pattern matching? • Reduced cognitive complexity ◦ Much more

    concise code and better readability ◦ More complex logic can be expressed with less lines of code ◦ Simpler to write and maintain • Reduced reliance on reflection and casting • Avoid pattern dominance • Pattern exhaustiveness which avoids bugs
  4. Pattern matching features • Enum matching in switch statements •

    Match type/value in switch statements • Match type/value in if statements • Pattern matched variable assignments • Null checks • Type guards • Refined patterns • Pattern dominance and type exhaustion • Partial/Nested/Compound type and/or value checks • Shallow/Deep Position-based Destructured matching
  5. Pattern matching in the wild Most modern languages support some

    level of pattern matching for data structures These are some languages with good support for it • Rust • OCaml • Haskell • C# • F# • Scala • Python • Ruby
  6. Pattern matching in the wild: Rust fn main() { let

    p = Point { x: 0, y: 7 }; match p { Point { x, y: 0 } => println!("On the x axis at {}", x), Point { x: 0, y } => println!("On the y axis at {}", y), Point { x, y } => println!("On neither axis: ({}, {})", x, y), } }
  7. Pattern matching in the wild: Scala def showNotification(notification: Notification): String

    = { notification match { case Email(sender, title, _) => s"You got an email from $sender with title: $title" case SMS(number, message) => s"You got an SMS from $number! Message: $message" case VoiceRecording(name, link) => s"You received a Voice Recording from $name! Click the link to hear it: $link" } }
  8. Pattern matching in the wild: C# public decimal CalculateDiscount(Order order)

    => order switch { (Items: > 10, Cost: > 1000.00m) => 0.10m, (Items: > 5, Cost: > 500.00m) => 0.05m, Order { Cost: > 250.00m } => 0.02m, null => throw new Exception(nameof(order), "Can't calculate discount on null order"), var someObject => 0m, };
  9. Pattern matching in Java Java is quite behind the curve

    when it comes to pattern matching. With Java 17 we have most of the building blocks • Switch statements • Switch expressions (Java 14) • Pattern matching for instanceof (Java 16) • Sealed classes (Java 17) • Pattern matching for switch (Java 17 preview - JEP 406)
  10. Match type in if statements // Before if (obj instanceof

    String) { String s = (String) obj; System.out.println(s.length()); } // After if (obj instanceof String s) { // Let pattern matching do the work! System.out.println(s.length()); }
  11. Match type guard for returns // Before public boolean equals(Object

    o) { if (!(o instanceof Point)) return false; Point other = (Point) o; return x == other.x && y == other.y; } // After public boolean equals(Object o) { return (o instanceof Point other) && x == other.x && y == other.y; }
  12. Match type guard for returns // Before var x =

    o instanceof Point ? ((Point)o).x : 0; System.out.println(x); // After var x = o instanceof Point p ? p.x : 0; System.out.println(x);
  13. Match type in if statements static String formatter(Object o) {

    String formatted = "unknown"; if (o instanceof Integer i) { formatted = String.format("int %d", i); } else if (o instanceof Long l) { formatted = String.format("long %d", l); } else if (o instanceof Double d) { formatted = String.format("double %f", d); } else if (o instanceof String s) { formatted = String.format("String %s", s); } return formatted; }
  14. Match type in switch cases (preview) static String formatter(Object o)

    { return switch (o) { case Integer i -> String.format("int %d", i); case Long l -> String.format("long %d", l); case Double d -> String.format("double %f", d); case String s -> String.format("String %s", s); default -> o.toString(); }; }
  15. Null checks with switch (preview) static String formatter(Object o) {

    return switch (o) { case null -> "Oops"; ... case String s -> String.format("String %s", s); default -> o.toString(); }; } or static String formatter(Object o) { return switch (o) { ... case String s -> String.format("String %s", s); case null, default -> "Oops"; }; }
  16. Type Guards & pattern refinement (preview) static void test(Object o)

    { if ((o instanceof String s) && s.length() > 3) { System.out.println(s); } else { System.out.println("Not a string "); } } Or static void test(Object o) { switch (o) { case String s && (s.length() > 3) -> System.out.println(s); case String s -> System.out.println("Invalid string "); default -> System.out.println("Not a string "); } }
  17. Sealed classes public abstract sealed class Shape permits Circle, Rectangle,

    Square { ... } Or public abstract sealed class Shape { final class Circle extends Shape { ... } final class Square extends Shape { ... } final class Rectangle extends Shape { ... } } A Sealed class/interface lets you control which class/interface can extend/implement them
  18. Sealed classes Constraints imposed on subclasses 1. Sealed class and

    its permitted subclasses must be in same module (same package in case of an unnamed module) 2. Every permitted subclass must directly extend the sealed class 3. Every permitted subclass must use a modifier to describe propagation of sealing (final, sealed, or non-sealed)
  19. Sealed classes public sealed interface Shape permits Circle, Rectangle, Square,

    WeirdShape { ... } public record Circle(int r) implements Shape { ... } public final class Square implements Shape { ... } public sealed class Rectangle implements Shape permits TransparentRectangle, FilledRectangle { ... } public non-sealed class WeirdShape implements Shape { ... }
  20. Sealed classes & pattern matching Shape rotate(Shape shape, double angle)

    { return switch (shape) { // this will be 'switch' expression does not cover all possible input values error case Circle c -> c; case Square s -> shape.rotate(angle); }; } Shape rotate(Shape shape, double angle) { return switch (shape) { case Circle c -> c; case Rectangle r -> shape.rotate(angle); case Square s -> shape.rotate(angle); case WeirdShape w -> shape.rotate(angle); // still exhaustive // no default needed! }; } Sealed classes will allow compiler to enforce exhaustive pattern matching
  21. Pattern matching features in Java • Enum matching in switch

    statements ✅ • Match type/value in switch statements ✅ • Match type/value in if statements ✅ • Pattern matched variable assignments ✅ • Null checks ✅ • Type guards ✅ • Refined patterns 🆗 • Pattern dominance and type exhaustion 🆗 • Partial/Nested/Compound type and/or value checks ❌ • Shallow/Deep Position-based Destructured matching ❌
  22. Future • Record Patterns & Array Patterns (Preview - JEP

    405) - JDK 18 candidate ◦ Destructure record patterns and values for instanceof operator ◦ Destructure array patterns and values for instanceof operator • Deconstruction for switch case patterns(maybe) • Support for primitives in switch case pattern (maybe) • Declare deconstruction patterns for classes (maybe)
  23. Record patterns record Point(int x, int y) {} void printSum(Object

    o) { if (o instanceof Point(int x, int y)) { System.out.println(x+y); } } Deconstruct patterns for records.
  24. Record patterns record Point(int x, int y) {} enum Color

    { RED, GREEN, BLUE } record ColoredPoint(Point p, Color c) {} void printSum(Object o) { if (o instanceof ColoredPoint(Point(int x, int y), Color c) { System.out.println(x+y); } } Deconstruct nested patterns for records
  25. Array patterns static void printFirstTwoStrings(Object o) { if (o instanceof

    String[] { String s1, String s2, ... }){ System.out.println(s1 + s2); } } Deconstruct patterns for arrays.
  26. Array + Record patterns static void printSumOfFirstTwoXCoords(Object o) { if

    (o instanceof Point[] { Point(var x1, var y1), Point(var x2, var y2), ... }) { System.out.println(x1 + x2); } } Deconstruct patterns for arrays of records.
  27. Future of Pattern matching features in Java • Enum matching

    in switch statements ✅ • Match type/value in switch statements ✅ • Match type/value in if statements ✅ • Pattern matched variable assignments ✅ • Null checks ✅ • Type guards ✅ • Refined patterns ✅ • Pattern dominance and type exhaustion 🆗 • Partial/Nested/Compound type and/or value checks 🆗 • Shallow/Deep Position-based Destructured matching 🆗
  28. Limitations of Pattern matching in Java • JDK 17 preview

    ◦ No deconstruction ◦ No nested patterns ◦ Type exhaustion is only for sealed classes and enums ◦ Pattern refinement is limited ◦ Still a preview feature • JDK 18 preview (maybe) ◦ No deconstruction for classes and in switch cases ◦ No nested patterns for classes and in switch cases ◦ Type exhaustion is only for sealed classes and enums ◦ No feature for ignoring don’t-care patterns during deconstruction ◦ No named patterns ◦ Still a preview feature
  29. Provide Feedback Pattern matching for switch: https://openjdk.java.net/jeps/406 Record Patterns &

    Array Patterns: https://openjdk.java.net/jeps/405 Discussions mailing list: [email protected]