Friends of OpenJDK Today

Increase readability and reduce complexity with Java’s Pattern Matching

March 14, 2024

Author(s)

  • Jonathan Vila

    Jonathan is a developer advocate at Sonar. He is a Java Champion and co-founder of the conferences in Spain JBCNConf and DevBcn and organizer of the Barcelona JUG. He has ... Learn more

Increase readability, reduce cognitive complexity, and avoid bugs that are hard to spot with Java's Pattern Matching.

I bet you don’t like writing ugly but necessary boilerplate code or reading it. But, sometimes we need to create logic that has to deal with an object of unknown type and follow different paths depending on the type. This code is prone to be too verbose, is complex to understand, and may involve some hidden errors hard to spot due to intermediate assignments.

In this article, I will show different ways of checking the type of an object and keeping the code easy to understand while also reducing the chances of introducing bugs hard to spot.

The usage of instanceOf

In Java, we’ve been using instanceOf conditional statements, type casting, and temporary assignments for that purpose.

public String processElement(Object element) {
  String result;

  if (element instanceOf String) { 
    String elementStr = (String) element;
    result = elementStr;
  } else if (element instanceOf Person) {
    Person elementPerson = (Person) element;
    result = elementPerson.getName();
  }

  return result + " value";
}

This involves a lot of boilerplate code which is not very readable. But even more important, it allows coding errors to remain hidden. In this structure, nothing is ensuring we are assigning a value to the intermediate variable result and that could mean having an empty value at the end.

But Java has included new features since version 14 that will help us to improve in this area. Let’s discover them.

Pattern Matching

In Java 16 an improvement was added in order to reduce code repetition and boilerplate: Pattern Matching for instanceOf cases. With this approach, the cast is included in the condition which is easier to read, reducing the boilerplate code.

public String processElement(Object element) {
  String result;

  if (element instanceOf String s) { 
   result = s;
  }

  if (element instanceOf Person elementPerson) {
    result = elementPerson.getName();
  }

  return result + " value";
}

With this change, we avoid the need for an extra type-cast, that is making the code harder to read, and even can involve more errors.

Yes, I agree with you, this is not solving the problem entirely. We have improved but we are not there yet. Let’s see if Java provides more tricks ...

Pattern matching in switch cases

In order to improve the readability a bit and reduce complexity we can use a switch/case statement. With this approach, we get rid of the “else if” clauses, making it clear that cases are exclusive and have different branches.

public String processElement(Object element) {
  String result;

  switch (element) {
   case (String s): 
     result = s;
     break;
   case (Person elementPerson):
    result = elementPerson.getName();
    break;
  }

  return result + " value";
}

But this code is still hard to read, I know. And it’s still weak in terms of errors that can happen by missing one break or by not assigning the value to the intermediate variable.

Switch expressions

In order to fix this situation we can use a very interesting feature included in Java 14: switch expressions. We will reduce the code even more, increase the readability and clarity, and avoid the bugs caused by missing intermediate assignments.

Also, we reduce the cognitive complexity of the resulting code by half and this positively impacts the readability and maintainability of the code. We need to keep in mind that too high complexity is one of the most common issues detected by Sonar tools in all the thousands of projects analyzed.

return switch (obj) {
    case Person person-> String.format("Person %s", person.getName());
    case String s -> String.format("Str %s", s);
    default -> obj.toString();
  } + " value";

With this approach, we have a very clear idea of what the code is doing and also reduce the risk of errors.

If you want to calculate the cognitive complexity of your code, you can use the "Code complexity" plugin (in IntelliJ) that will give you a hint of your method’s complexity.

private void getStringsUsingInstanceOfIfs(Object user) { @ simple(25%)
    ...
}

private void getStringsUsingSwitchExpressionPattern(Object user) { @ simple(0%)
    ...
}

The Sonar Java analyzer will warn you if your code has too high complexity, and also will suggest using the switch pattern matching approach and the switch expression in order to improve readability.

Conclusions

We spend way more time reading code than writing it, so it’s super important to make our code conventional and intentional in order to help us understand its purpose.

The Java language adds new features in every release to help you write consistent, simple, and robust code providing standardized ways of solving common issues and reducing the time to understand the purpose of the code and the probability of errors.

Sponsored Content

Jakarta EE 11: Beyond the Era of Java EE

This user guide provides a brief history of Java EE/Jakarta EE and a detailed overview of some of the specifications that will be updated in Jakarta EE 11.

Get Started

Topics:

Related Articles

View All

Author(s)

  • Jonathan Vila

    Jonathan is a developer advocate at Sonar. He is a Java Champion and co-founder of the conferences in Spain JBCNConf and DevBcn and organizer of the Barcelona JUG. He has ... Learn more

Comments (5)

Your email address will not be published. Required fields are marked *

Highlight your code snippets using [code lang="language name"] shortcode. Just insert your code between opening and closing tag: [code lang="java"] code [/code]. Or specify another language.

Save my name, email, and website in this browser for the next time I comment.

Harald

I am looking forward to using such patterns, but it is blatantly obvious from the example how sorely Java is missing the possibility to create arbitrary union types. The method signature should be

 
public String processElement(String | Person element) {

for best readability. Seeing Object as the parameter type in production code would make me extremely nervous.

Jonathan Vila

@Harald Thanks a lot for your comment.
I agree that having union types would have made the method signature clearer. The example is an oversimplification, but in real life, you could use a common parent in the signature, or use sealed classes (https://spin.atomicobject.com/java-sealed-interface/)
In any case that would not change the use of the pattern matching.
But I totally support the union types as a way to define the list of possible types.

Java Weekly, Issue 534 – All About Java 22 | Baeldung

[…] >> Increase readability and reduce complexity with Java’s Pattern Matching [foojay.io] […]

Raghav

Really a great article the way it explained about pattern matching and switch expressions. Really Helpful and informative

Java Annotated Month-to-month – April 2024

[…] Improve readability and cut back complexity with Java’s Sample Matching – Jonathan Vila reveals other ways of checking the kind of an object and preserving the code straightforward to know whereas additionally decreasing the probabilities of introducing hard-to-spot bugs. […]

Subscribe to foojay updates:

https://foojay.io/feed/
Copied to the clipboard