Reader small image

You're reading from  Java Coding Problems - Second Edition

Product typeBook
Published inMar 2024
PublisherPackt
ISBN-139781837633944
Edition2nd Edition
Right arrow
Author (1)
Anghel Leonard
Anghel Leonard
author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard

Right arrow

Records and Record Patterns

This chapter includes 19 problems that cover, in detail, the Java records introduced in JDK 16 (JEP 395), and record patterns introduced as a preview feature in JDK 19 (JEP 405), as a second preview feature in JDK 20 (JEP 432), and as a final feature in JDK 21 (JEP 440).

We start by defining a simple Java record. We continue by analyzing a record’s internals, what it can and cannot contain, how to use records in streams, how they improve serialization, and so on. We are also interested in how we can use records in Spring Boot applications, including JPA and jOOQ technologies.

Next, we focus on record patterns for instanceof and switch. We will talk about nested record patterns, guarded record patterns, handling null values in record patterns, and so on.

At the end of this chapter, you’ll have mastered Java records. This is great because records are a must-have for any Java developer who wants to adopt the coolest Java features...

Problems

Use the following problems to test your programming prowess on Java records. I strongly encourage you to give each problem a try before you turn to the solutions and download the example programs:

  1. Declaring a Java record: Write an application that exemplifies the creation of a Java record. Moreover, provide a short description of the artifacts generated by the compiler for a record behind the scenes.
  2. Introducing the canonical and compact constructors for records: Explain the role of the built-in record’s canonical and compact constructors. Provide examples of when it makes sense to provide such explicit constructors.
  3. Adding more artifacts in a record: Provide a meaningful list of examples about adding explicit artifacts in Java records (for instance, adding instance methods, static artifacts, and so on).
  4. Iterating what we cannot have in a record: Exemplify what we cannot have in a record (for instance, we cannot have explicit private fields...

88. Declaring a Java record

Before diving into Java records, let’s think a little bit about how we commonly hold data within a Java application. You’re right … we define simple classes containing the needed instance fields populated with our data via the constructors of these classes. We also expose some specific getters, and the popular equals(), hashCode(), and toString() methods. Further, we create instances of these classes that wrap our precious data and we pass them around to solve our tasks all over our application. For instance, the following class carries data about melons like the melon types and their weights:

public class Melon {
  private final String type;
  private final float weight;
  public Melon(String type, float weight) {
    this.type = type;
    this.weight = weight;
  }
  public String getType() {
    return type;
  }
  public float getWeight() {
    return weight;
  }
  // hashCode(), equals(), and to String()
}

You should be...

89. Introducing the canonical and compact constructors for records

In the previous problem, we created the MelonRecord Java record and we instantiated it via the following code:

MelonRecord melonr = new MelonRecord("Cantaloupe", 2600);

How is this possible (since we didn’t write any parameterized constructor in MelonRecord)? The compiler just followed its internal protocol for Java records and created a default constructor based on the components that we provided in the record declaration (in this case, there are two components, type and weight).

This constructor is known as the canonical constructor and it is always aligned with the given components. Every record has a canonical constructor that represents the only way to create instances of that record.

But, we can redefine the canonical constructor. Here is an explicit canonical constructor similar to the default one – as you can see, the canonical constructor simply takes all the given...

90. Adding more artifacts in a record

So far, we know how to add an explicit canonical/compact constructor into a Java record. What else can we add? Well, for example, we can add instance methods as in any typical class. In the following code, we add an instance method that returns the weight converted from grams to kilograms:

public record MelonRecord(String type, float weight) {
  public float weightToKg() {
    return weight / 1_000;
  }
}

You can call weightToKg() exactly as you call any other instance method of your classes:

MelonRecord melon = new MelonRecord("Cantaloupe", 2600);
// 2600.0 g = 2.6 Kg
System.out.println(melon.weight() + " g = " 
  + melon.weightToKg() + " Kg"); 

Besides instance methods, we can add static fields and methods as well. Check out this code:

public record MelonRecord(String type, float weight) {
  private static final String DEFAULT_MELON_TYPE = "Crenshaw";
  private static final float...

91. Iterating what we cannot have in a record

There are several artifacts that we cannot have in a Java record. Let’s tackle the top 5 one by one.

A record cannot extend another class

Since a record already extends java.lang.Record and Java doesn’t support multiple inheritances, we cannot write a record that extends another class:

public record MelonRecord(String type, float weight) 
  extends Cucurbitaceae {…}

This snippet doesn’t compile.

A record cannot be extended

Java records are final classes, so they cannot be extended:

public class PumpkinClass extends MelonRecord {…}

This snippet doesn’t compile.

A record cannot be enriched with instance fields

When we declare a record, we also provide the components that become the instance fields of the record. Later, we cannot add more instance fields as we could in a typical class:

public record MelonRecord(String type, float weight) {
  private String...

92. Defining multiple constructors in a record

As you know, when we declare a Java record, the compiler uses the given components to create a default constructor known as the canonical constructor. We can also provide an explicit canonical/compact constructor, as you saw in Problem 89.

But, we can go even further and declare more constructors with a different list of arguments. For example, we can have a constructor with no arguments for returning a default instance:

public record MelonRecord(String type, float weight) {
  private static final String DEFAULT_MELON_TYPE = "Crenshaw";
  private static final float DEFAULT_MELON_WEIGHT = 1000;
  MelonRecord() {
    this(DEFAULT_MELON_TYPE, DEFAULT_MELON_WEIGHT);
  } 
}

Or, we can write a constructor that gets only the melon’s type or the melon’s weight as an argument:

public record MelonRecord(String type, float weight) {
  private static final String DEFAULT_MELON_TYPE = "Crenshaw";...

93. Implementing interfaces in records

Java records cannot extend another class but they can implement any interface exactly like a typical class. Let’s consider the following interface:

public interface PestInspector {
  public default boolean detectPest() {
    return Math.random() > 0.5d;
  }
  public void exterminatePest();
}

The following snippet of code is a straightforward usage of this interface:

public record MelonRecord(String type, float weight)   
       implements PestInspector {
  @Override
  public void exterminatePest() {  
    if (detectPest()) {
      System.out.println("All pests have been exterminated");
    } else {
      System.out.println(
        "This melon is clean, no pests have been found");
    }
  }
}

Notice that the code overrides the abstract method exterminatePest() and calls the default method detectPest().

94. Understanding record serialization

In order to understand how Java records are serialized/deserialized, let’s have a parallel between classical code based on plain Java classes and the same code but expressed via the Java record’s syntactical sugar.

So, let’s consider the following two plain Java classes (we have to explicitly implement the Serializable interface because, in the second part of this problem, we want to serialize/deserialize these classes):

public class Melon implements Serializable {
  private final String type;
  private final float weight;
  public Melon(String type, float weight) {
    this.type = type;
    this.weight = weight;
  }
  // getters, hashCode(), equals(), and toString()
}

And, the MelonContainer class that uses the previous Melon class:

public class MelonContainer implements Serializable {
  private final LocalDate expiration;
  private final String batch;
  private final Melon melon;
  public MelonContainer...

95. Invoking the canonical constructor via reflection

It is not a daily task to invoke the canonical constructor of a Java record via reflection. However, this can be accomplished quite easily starting with JDK 16, which provides in java.lang.Class the RecordComponent[] getRecordComponents() method. As its name and signature suggest, this method returns an array of java.lang.reflect.RecordComponent representing the components of the current Java record.

Having this array of components, we can call the well-known getDeclaredConstructor() method to identify the constructor that gets as arguments exactly this array of components. And that is the canonical constructor.

The code that puts these statements into practice is provided by the Java documentation itself, so there is no need to reinvent it. Here it is:

// this method is from the official documentation of JDK
// https://docs.oracle.com/en/java/javase/19/docs/api/
// java.base/java/lang/Class.html#getRecordComponents...

96. Using records in streams

Consider the MelonRecord that we have used before:

public record MelonRecord(String type, float weight) {}

And a list of melons as follows:

List<MelonRecord> melons = Arrays.asList(
  new MelonRecord("Crenshaw", 1200),
  new MelonRecord("Gac", 3000), 
  new MelonRecord("Hemi", 2600),
  ...
);

Our goal is to iterate this list of melons and extract the total weight and the list of weights. This data can be carried by a regular Java class or by another record as follows:

public record WeightsAndTotalRecord(
  double totalWeight, List<Float> weights) {}

Populating this record with data can be done in several ways, but if we prefer the Stream API then most probably we will go for the Collectors.teeing() collector. We won’t go into too much detail here, but we’ll quickly show that it is useful for merging the results of two downstream collectors. (If you’re interested,...

97. Introducing record patterns for instanceof

In order to introduce record patterns, we need a more complex record than the one we’ve used so far, so here’s one:

public record Doctor(String name, String specialty) 
  implements Staff {}

This record implements the Staff interface as any other employee of our hospital. Now, we can identify a certain doctor in the old-fashioned style via instanceof as follows:

public static String cabinet(Staff staff) {
  if (staff instanceof Doctor) {
    Doctor dr = (Doctor) staff;
    return "Cabinet of " + dr.specialty() 
      + ". Doctor: " + dr.name();
  }
  ...
}

But, as we know from Chapter 2, Problems 58-67, JDK has introduced type patterns that can be used for instanceof and switch. So, in this particular case, we can rewrite the previous code via type patterns as follows:

public static String cabinet(Staff staff) {
  if (staff instanceof Doctor dr) { // type pattern matching
    return...

98. Introducing record patterns for switch

You already know that type patterns can be used for instanceof and switch expressions. This statement is true for record patterns as well. For instance, let’s reiterate the Doctor and Resident records:

public record Doctor(String name, String specialty) 
  implements Staff {}
public record Resident(String name, Doctor doctor) 
  implements Staff {}

We can easily use these two records via record patterns in a switch expression as follows:

public static String cabinet(Staff staff) {
 return switch(staff) {  
  case Doctor(var name, var specialty) 
    -> "Cabinet of " + specialty + ". Doctor: " + name;
  case Resident(var rsname, Doctor(var drname, var specialty)) 
    -> "Cabinet of " + specialty + ". Doctor: " 
                     + drname + ", Resident: " + rsname;
  default -> "Cabinet closed";
 }; 
}

Adding more nested records follows the same...

99. Tackling guarded record patterns

Exactly as in the case of type patterns, we can add guarding conditions based on the binding variables. For instance, the following code uses guarding conditions with instanceof for determining if the Allergy cabinet is open or closed (you should be familiar with the Doctor record from the previous two problems):

public static String cabinet(Staff staff) {
  if (staff instanceof Doctor(String name, String specialty) 
       && (specialty.equals("Allergy") 
       && (name.equals("Kyle Ulm")))) { 
     return "The cabinet of " + specialty 
       + " is closed. The doctor " 
       + name + " is on holiday.";
  }                
  if (staff instanceof Doctor(String name, String specialty) 
      && (specialty.equals("Allergy") 
      && (name.equals("John Hora")))) { 
    return "The cabinet of " + specialty 
      + " is open...

100. Using generic records in record patterns

Declaring a generic record for mapping fruit data can be done as follows:

public record FruitRecord<T>(T t, String country) {}

Now, let’s assume a MelonRecord, which is a fruit (actually, there is some controversy over whether a melon is a fruit or a vegetable, but let’s say that it is a fruit):

public record MelonRecord(String type, float weight) {}

We can declare a FruitRecord<MelonRecord> as follows:

FruitRecord<MelonRecord> fruit = 
  new FruitRecord<>(new MelonRecord("Hami", 1000), "China");

This FruitRecord<MelonRecord> can be used in record patterns with instanceof:

if (fruit instanceof FruitRecord<MelonRecord>(
    MelonRecord melon, String country)) {
  System.out.println(melon + " from " + country);
} 

Or, in switch statements/expressions:

switch(fruit) {
  case FruitRecord<MelonRecord>(
       MelonRecord...

101. Handling nulls in nested record patterns

From Chapter 2, Problem 54, Tackling the case null clause in switch, we know that starting with JDK 17 (JEP 406), we can treat a null case in switch as any other common case:

case null -> throw new IllegalArgumentException(...);

Moreover, from Problem 67, we know that, when type patterns are involved as well, a total pattern matches everything unconditionally including null values (known as an unconditional pattern). Solving this issue can be done by explicitly adding a null case (as in the previous snippet of code) or relying on JDK 19+. Starting with JDK 19, the unconditional pattern still matches null values only it will not allow the execution of that branch. The switch expressions will throw a NullPointerException without even looking at the patterns.

This statement partially works for record patterns as well. For instance, let’s consider the following records:

public interface Fruit {}
public record SeedRecord...

102. Simplifying expressions via record patterns

Java records can help us to simplify snippets of code meant to handle/evaluate different expressions (mathematical, statistical, string-based, Abstract Syntax Tree (AST), and so on) a lot. Typically, evaluating such expressions implies a lot of conditions and checks that can be implemented via if and/or switch statements.

For example, let’s consider the following records meant to shape string-based expressions that can be concatenated:

interface Str {}
record Literal(String text) implements Str {}
record Variable(String name) implements Str {}
record Concat(Str first, Str second) implements Str {}

Some parts of the string expression are literals (Literal) while others are provided as variables (Variable). For brevity, we can evaluate these expressions only via the concatenation operation (Concat), but feel free to add more operations.

During the evaluation, we have an intermediary step for simplifying the expression...

Join our book community on Discord

https://packt.link/vODyi

Qr code Description automatically generated

This chapter includes 20 problems covering different date-time topics. These problems are mainly focused on the Calendar API and on the JDK 8 Date/Time API. About the latter, we will cover less popular APIs such as ChronoUnit, ChronoField, IsoFields, TemporalAdjusters, and so on.At the end of this chapter, you'll have under your tool belt a ton of tips and tricks that are very useful to solve a wide range of date-time real problems.

Problems

Use the following problems to test your programming prowess on date and time. I strongly encourage you to give each problem a try before you turn to the solutions and download the example programs:

  1. Defining a day period: Write an application that goes beyond AM/PM flags and split the day into four periods: night, morning, afternoon, and evening. Depending on the given date-time and time zone generate one of these periods.
  2. Converting between Date and YearMonth: Write an application that converts between java.util.Date and java.time.YearMonth and vice-versa.
  3. Converting between int and YearMonth: Let's consider that a YearMonth is given (for instance, 2023-02). Convert it to an integer representation (for instance, 24277) that can be converted back to YearMonth.
  4. Converting week/year to Date: Let's consider that two integers are given representing a week and a year (for instance, week 10, year 2023). Write a program that converts 10-2023 to a java.util.Date via Calendar...

70. Extracting the months from a given quarter

This problem becomes quite accessible if we are familiar with the JDK 8, java.time.Month. Via this API, we can find the first month (0 for January, 1 for February, …) of a quarter containing the given LocalDate as Month.from(LocalDate).firstMonthOfQuarter().getValue().Once we have the first month is easy to obtain the other two as follows:

public static List<String> quarterMonths(LocalDate ld) {
  List<String> qmonths = new ArrayList<>();
  int qmonth = Month.from(ld)
    .firstMonthOfQuarter().getValue();
  qmonths.add(Month.of(qmonth).name());
  qmonths.add(Month.of(++qmonth).name());
  qmonths.add(Month.of(++qmonth).name());
       
  return qmonths;
}

How about passing as argument the quarter itself? This can be done as a number (1, 2, 3, or 4) or as a string (Q1, Q2, Q3, or Q4). If the given quarter is a number then the first month of the quarter can be obtained as quarter * 3 – 2, where the quarter...

Summary

Mission accomplished! I hope you enjoyed this short chapter filled to the brim with tips and tricks about manipulating date-time in real word applications. I strongly encourage you to also read the homologous chapter from Java Coding Problems, First Edition, which also contains 20 problems covering date-time topics.

Summary

The goal of this chapter was to deeply cover Java records and record patterns. We have assigned the same importance to both the theoretical and the practical parts so that, in the end, there are no secrets regarding the use of these two topics. And, just in case you wonder why we didn’t cover the topic regarding record patterns appearing in the header of an enhanced for statement, then please notice that this was added as a preview in JDK 20 but it was removed in JDK 21. This feature may be re-proposed in a future JEP.

Leave a review!

Enjoying this book? Help readers like you by leaving an Amazon review. Scan the QR code below for a 20% discount code.

*Limited Offer

lock icon
The rest of the chapter is locked
You have been reading a chapter from
Java Coding Problems - Second Edition
Published in: Mar 2024Publisher: PacktISBN-13: 9781837633944
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $15.99/month. Cancel anytime

Author (1)

author image
Anghel Leonard

Anghel Leonard is a Chief Technology Strategist and independent consultant with 20+ years of experience in the Java ecosystem. In daily work, he is focused on architecting and developing Java distributed applications that empower robust architectures, clean code, and high-performance. Also passionate about coaching, mentoring and technical leadership. He is the author of several books, videos and dozens of articles related to Java technologies.
Read more about Anghel Leonard