Saturday, November 12, 2022

Java 19: Record Patterns

Java 19 introduces Record Patterns, a preview language feature, which allow us to "deconstruct" records and access their components directly.

Here is an example:

record Book(String title, String author, double price) {}

static void print(Book b) {
  if (b instanceof Book(String title, String author, double price)) {
    System.out.printf("%s by %s for %.2f", title, author, price);
  }
}

Book(String title, String author, double price) is a record pattern that decomposes an instance of a Book record into its components: title, author and price.

null does not match any record pattern.

Record patterns can be used in switch statements and expressions as well:

static double getPrice(Publication p) {
  return switch(p) {
    case Book(var title, var author, var price) -> price;
    case Magazine(var title, var publisher, var price) -> price;
    default -> throw new IllegalArgumentException("Invalid publication: " + p);
  };
}

Note that I have used var inside the record patterns, allowing the compiler to infer the type of each component, thus saving me from having to explicitly state it.

Record patterns can also be nested inside one another, in order to decompose complicated object graphs. For example:

record Author(String firstName, String lastName) {}
record Book(String title, Author author, double price) {}

static void print(Book b) {
  if (b instanceof Book(var title, Author(var firstName, var lastName), var price)) {
    System.out.printf("%s by %s %s for %.2f", title, firstName, lastName, price);
  }
}

Saturday, November 05, 2022

Java 19: Guarded Patterns in Switch

Previously, I wrote about how switch statements and expressions had been enhanced to match on type patterns, and also how "guarded patterns" can be used to refine a pattern so that it is only matched on certain conditions.

In Java 19, the syntax of the guarded pattern has been changed so that instead of using &&, you need to use a when clause, as shown in the example below.

static String guardedPattern(Collection<String> coll) {
  return switch(coll) {
    case null -> 
      "Collection is null!";
    case List list 
    when list.size() > 10 ->
      "I am a big List. My size is " + list.size();
    case List list ->
      "I am a small List. My size is " + list.size();
    default -> 
      "Unsupported collection: " + coll.getClass();
  };
}

As an aside, it's worth pointing out how nulls are handled within the switch block. The default label does NOT match nulls, so you need to explicitly add a case null, otherwise you will get a NullPointerException. This is for backwards compatibility with the current semantics of switch.

Related post:
Java 17: Pattern Matching for Switch