The final releases of records and the new pattern matching functionality for instanceof are planned for JDK 16. With these new features, boilerplate code can be reduced and code becomes less error-prone.
Both features were already available as a preview in JDK 14 and 15. They are slightly improved and should be available in JDK 16 whose release is planned for March 2021. If all goes to plan, then the new features will be available in JDK 17, the next long term support version, expected in September 2021.
Records are the Java version of named tuples - basically containers for an ordered sequence of elements. When using records, it's no longer possible to decouple the API and the representation - both are derived from the record declaration.
One of the main effects of this feature is that it reduces boilerplate code. Until now classes with constructors and methods like getters, setters, equals
, toString
and hashCode
had to be written by hand. Some projects were using libraries such as Lombok to reduce the amount of boilerplate code. Unfortunately, Lombok needs extra support in the form of IDE plugins. The new records feature should make this process easier and less error-prone.
To get started, a record should be defined:
record Name(String firstName, String lastName){}
Records can be declared in a separate file or as part of another class. There are many use cases for records; they can be used to return multiple values from a method, as compound keys in a map, or simply as data objects.
When a normal class is defined, then implicitly a default constructor is added. Records have similar behavior and implicitly contain a number of members:
- Private fields for all components of the record.
- Public accessor methods for all components of the record.
- Constructor with arguments for all components of the record. This constructor is named the canonical constructor.
equals
,toString
, andhashCode
methods with their implementation.
The record defined earlier doesn't contain any custom code, but it's possible to define explicit methods. It's even possible to explicitly declare the various implicitly added methods and the canonical constructor. For example, it's possible to create a custom toString
method.
In JDK 14 the canonical constructor was required to be public. From JDK 15 onwards the implicit canonical constructor gets the same access modifier as the record class. When defining an explicit canonical constructor, the access modifier has to provide at least as much access as the record class.
As part of the change, the @Override
annotation was changed as well. It can now be used to annotate an explicit accessor method in a record.
The Java 14 Feature Spotlight:Records, written by Brian Goetz, provides some more details and examples for records.
In regards to pattern matching for instanceof, verifying if an object was of a certain type and then using it was always a two-step process. First, instanceof
was used to check if the argument was of the correct type and then the object was cast to that specific type. After that, object-specific methods can be called such as the pay() method on a Customer:
if (person instanceof Customer) {
Customer customer = (Customer) person;
customer.pay();
}
With the new pattern matching feature, it's possible to write the same functionality in a more compact form. In this case, the customer is the so-called pattern variable. A pattern variable is in scope when it's successfully matched:
if (person instanceof Customer customer) {
customer.pay();
}
Now the pattern matching feature mainly reduces boilerplate code. However, this functionality will be used for switch expressions in a future Java version.
Pattern variables can shadow fields. For instance, the customer pattern variable can shadow the customer field:
// Field customer in scope
if (person instanceof Customer customer) {
// customer refers to the pattern variable
} else {
// customer refers to the field
}
The pattern matching instanceof
expression can be combined with other expressions to write more compact methods such as the equals
method:
@Override
public boolean equals(Object o) {
return o instanceof Customer other &&
name.equals(other.name);
}
Instead of the old way:
@Override
public boolean equals(Object o) {
if (o instanceof Customer) {
Customer other = (Customer) o;
if (name.equals(other.name)) {
return true;
}
}
return false;
}