Deep dive into Pattern Matching for Java

across other JVM languages and a variety of programming paradigms and design principles

meowpunch
meowailand
5 min readJan 13, 2022

--

Photo by Shot by Cerqueira on Unsplash

Intro

On 14 Sep 2021, Oracle released production-ready JDK 17 as the latest long-term support (LTS) version. At the same time, they announced to change the ongoing LTS release cadence from three years to two years.

[VDT21] Java 17: New and Exciting! by Simone Bordet

The most interesting new features since Java 11 are pattern matching, sealed classes and records, which are enhancements from Project Amber in order to increase developer productivity by evolving the Java language. Seal classes and records were already delivered as JEP 409 and JEP 395 (If you don’t know records and sealed classes, it would be nice if you check them). As JEP 395 stated below, they planned that two features would interoperate with pattern matching.

In addition to the combination of record classes and sealed classes, record classes lend themselves naturally to pattern matching. Because record classes couple their API to their state description, we will eventually be able to derive deconstruction patterns for record classes as well, and use sealed type information to determine exhaustiveness in switch expressions with type patterns or deconstruction patterns.

Pattern matching is not fully supported yet and JEP 420 (Pattern Matching for switch) and JEP 405 (Record Patterns & Array Patterns) are currently in progress.

As the person who was interested in functional programming, it was impressive for me to support these kinds of features in Java. I will explore what pattern matching of Java is, with comparing to other JVM languages, Scala and Kotlin, and looking through its meanings under a variety of programming paradigms and design principles.

  • Pattern matching, comparing with JVM languages (Java, Kotlin, Scala)
  • Meanings under programming paradigms and design principles

Pattern matching for Java

Motivation

Before comparing pattern matching between JVM languages, Let’s find out its necessity with an example code in Clean Code, Robert C. Martin.

Snippet 1: Procedural Shape, Chapter 6: Objects and Data Structures, Clean Code

You might feel uncomfortable with the above code, especially if you are an object-oriented programmer. However, I will try to figure out the problems without object-oriented approach at least for now. (You can see an object-oriented consideration in functional vs object-oriented section)

The area method repeats three processes: type test(check), type conversion(cast) and deconstruction(extract). This redundancy disturbs a developer to focus on business logic and makes the code error-prone and unsafe. Furthermore, the more types you test, the more boilerplate codes you get.

I will improve the code in two ways, which are newly introduced in Java.

  • pattern matching for instanceof
  • pattern matching for switch

JEP 394 : Pattern Matching for instanceof

Kotlin supports smart casts. When checking type with is operator, the compiler explicit casts for immutable values and inserts casts automatically and safely.

Smart cast, `is` operator, Kotlin

Similarly, Java extended instanceof operator to type patterns, which enables to perform instanceof test, cast the target, and bind it to a pattern variable. I can refactor area method with type patterns.

Type patterns, `instanceof` operator, Java

Through refactoring, I already achieved the improvement. But if you imagine the complex relationships and hierarchies between domain entities in real life, the nested if else structure will appear redundant. Even though Java already supported switch for a multi-armed equality test, switch had a lot of limitations.

JEP 406 : Pattern Matching for switch (Preview)

Java 11 had only switch statements, not expressions. It tests for exact equality against numeric, string, or enum constants. Currently you can use switch expressions feature which was delivered since Java 14.

statement vs expression with `switch`

A statement describes an action to be carried out, whereas an expression describes a value to be evaluated. It can be told that the part with statement is imperative and other part with expression is declarative. In above case, switch expression makes the code more concise and safer.

In addition to switch expressions, switch was extended to support type patterns. It means that switch can test and cast a type of object and bind it to a new variable through case label. Finally, switch can be applied to the example of Clean Code.

Snippet 2: procedural code improved with pattern matching, seal classes and records

The code is much clearer and more understandable, as tidying up lots of repetitive code which obfuscates the more significant logic. Also, it is optimised in terms of performance. (Let # of types be N, O(N) in if else to roughly constant time, O(1) in switch)

Deep dive into the meanings

Functional vs Object-oriented

As every approach has tradeoffs, the above code improved with pattern matching is not an absolute solution. Interestingly, this topic is dealt with not only in Clean Code, which is base on an OOP, but also in Functional Programming Principles in Scala by Martin Odersky.

In Clean Code, the Procedural Shape, Snippet 1 is refactored to the below Polymorphic Shape, Snippet 3 having dynamic dispatch, which is the process of selecting which implementation of a method to call at run time.

Snippet 3: Polymorphic Shape, Chapter 6: Objects and Data Structures, Clean Code

Despite of refactoring, the author is not biased towards one of two types. From the perspective of OCP (Open Closed Principle) in design principles and design patterns, the author only pursues maximisation of extension and minimisation of modification while weighing procedural and OO codes according to use cases.

Procedural code (code using data structures) makes it easy to add new functions without changing the existing data structures. OO code, on the other hand, makes it easy to add new classes without changing existing functions.

Going further, in Functional Programming Principles in Scala, the procedure code is improved to functional decomposition with pattern matching as in Java, and comparing it with object-oriented decomposition.

Pattern Matching, Functional Programming Principles in Scala, Martin Odersky

Deconstruction Patterns

Truly someone who used to love Scala might not be surprised at pattern matching for Java, as the feature was already being supported as functional tendencies in Scala more than 10 years ago. Scala docs describes pattern matching as follows.

Pattern matching is a mechanism for checking a value against a pattern. A successful match can also deconstruct a value into its constituent parts. It is a more powerful version of the switch statement in Java and it can likewise be used in place of a series of if/else statements.

Previously we have already combined type check and cast of three processes: type test(check), type conversion(cast) and deconstruction(extract). As the docs mentioned above, a successful pattern matching should combine all the process including deconstruction. Snippet 2 of Java is refactored to Snippet 4 of Scala

Snippet 4: area method Improved with deconstruction patterns

Above the values(states) of case class can be extracted through the deconstruction pattern along with the type pattern. In Java, JEP 405 (Record Patterns & Array Patterns) is being developed to deconstruct record values and array values. Pattern matching with the feature can be more powerful especially for more complicated object graphs.

record patterns to deconstruct record values

It lets us decompose objects without worrying about null or NullPointerException. As above, nested record patterns can extract data from objects far more smoothly and concisely than traditional imperative code.

Conclusion

I hope that I will be able to utilise Java’s record patterns and array patterns as soon as possible. It is unlikely that the Java organization will support functional programming, but enabling the implementation of declarative code through record class and pattern matching will certainly provide additional options to programmers. I’m looking forward to not only the next LTS version, but also great synergies with the innovations from the Java camp like Project Loom and Graalvm.

Please feel free to comment on any mistakes and complements 🙌🏽

Reference

--

--