Java 14 — New Language Features

Giorgi Tsiklauri
Javarevisited
Published in
13 min readSep 14, 2021

--

Quite a few updates have been introduced in the Java 14 language; namely, two Preview, one Second Preview, and one finalized feature are what you’ll see as new features in Java 14.

These are the news we will be learning, in-depth, today:

  • Pattern Matching For instanceof (Preview) (JEP 305)
  • Records (Preview) (JEP 359)
  • Switch Expressions (Standard) (JEP 361)
  • Text Blocks (Second Preview) (JEP 368)

I will explain each at a time, and I will try to go as deep as possible.

Pattern Matching for instanceof (Preview) (JEP 305)

We all have used instanceof relational operator, to check, in the runtime, whether some object is an instance of the particular type.

Let’s recap this briefly:
instanceof operator, like all other relational operators, forms a boolean expression, general syntax of which looks as:

a instanceof B

and it checks whether the object (a) is an instance of the particular type (B), subtype, or a class that implements the interface. If it is, the expression evaluates to true, and to false — otherwise.

  • a can be (1) reference variable, (2) class instantiation expression, (3) enum constant, or (4) null;
  • B can be a (1) class name, (2) interface name, or (3) enum name;
  • a and B must be convertible types — they must be in the inheritance tree relationship, otherwise, it’s a compile-time error.

So, if, say, your method accepts a parameter of type java.lang.Object, or you iterate over a collection of a bit general type that can contain instances of subtypes, or if there is any other situation, where you are not sure object of which specific type you will have to handle at runtime, you may first want to exactly know whether the object is an instance of the particular type X; otherwise, you won’t be able to safely downcast your object and access (operate on) its members.

So, you have to write something like this:

if (obj instanceof X) {
X x = (X)obj;
//...
}

Enough recapping. Now let’s see what could be improved in the above example. It contains:

  1. Testing whether obj is instance of X;
  2. if so, then casting obj to the X type;
  3. Declare a variable of the type in a question and assign to it, the casted object (if ClassCastException does not happen).

Don’t you think these three steps constitute a repetitive ceremony you always have (had! before now) to do, in order to be able to use the received object (that is to what x refers, after casting) whose type is not certain at compile time?

Avoiding this tedious ceremony is the motivation for Pattern Matching for instanceof feature, which suggests, that whenever type checking boolean expression evaluates to true (i.e. instance happens to be the type of whatever it is checked against), type casting should be unnecessary, because if you test whether the object is the instance of a particular type, the only thing you might possibly want to do with that object, is to cast it to the type in question.

Therefore, in order to concentrate more on logic, rather than on a verbosity of the language semantics (that was practically always necessary whenever you used instanceof), pattern matching proposes a newer, more concise syntax, which, for the same example as above, looks as:

if (obj instanceof X x) {
//x is in scope
} else {
//x is NOT in scope
}

In this syntax:

  1. whenever obj instanceof X is true, obj reference is casted to the type X and it is then assigned to x;
  2. Note, that scope of a binding variable (that is x, in this case) is determined by the semantics of the boolean expression of if, and it’s going to be in the scope of a true block only.
    In the above example, if obj is NOT an instance of X, you won’t be using it as an object of type X, stored in x., in the block that immediately follows if; however, if it was !(obj instanceof X x) then the true block would have been else, and that is where the binding variable x would have only been in scope;
  3. If the condition of the if statement is a compound boolean expression (composed of && or ||), then the binding variable (that is declared with type against which you’re checking instanceof) is in the scope on the right hand side of && and in the true block, but it is not in the scope if it’s on the right hand side of ||.

Formally:

  1. A pattern is a combination of (1) a predicate that can be applied to a target, and (2) a set of binding variables that are extracted from the target only if the predicate successfully applies to it;
  2. A type test pattern consists of a predicate that specifies a type, along with a single binding variable;
  3. The instanceof operator is extended to take a type test pattern instead of just a type. In the above example, the phrase X x is the type test pattern.

Records (Preview) (JEP 359)

For a very long time, Java had been accused of being very verbose and lengthy, when it comes to syntax.

For example, if you only wanted to create a simple POJO class, that models Employee with only three fields: name, surname, and position, you should have written this long code:

public class Employee {

private String name;
private String surname;
private Position position;

public Employee() {
}
public Employee(String n, String s, Position p) {

this.name = n;
this.surname = s;
this.position = p;

}

public String getName() {
return this.name;
}

public String getSurname() {
return this.name;
}
public Position getPosition() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public void setSurname(String surname) {
this.surname = surname;
}

public void setPosition(Position p) {
this.position = p;
}

//toString..
//hashCode.. //equals..}

All this code.. constructors, getters, setters, equals(), hashCode(), toString().. is really too much for creating just a data carrier class, instances of which, you will use for carrying data from one layer to another; from one abstraction to another; from one storage to another.

Because of this, very often, developers forget, omit, or just disregard some very important methods (which they underestimate), like hashCode() or equals().

Yes, modern IDEs do a lot of code scaffolding/generating, but still, reading such a verbose code, keeping it and having the maintenance responsibility over it — is a big, abundant and redundant cost.

Welcome, records!

Record is a new kind of type in Java language - a restricted form of class (like enum) that acts as a transparent carrier/aggregate of immutable data.

It’s just a data carrier, plain data aggregate type, instances of which hold, and are used to carry values/data. Records are not as functional as traditional classes are, but they constitute a super concise, short and cleanly-coded types, when it comes to having a pure data aggregate types.

Remember: records let you define classes which are transparent containers of shallowly immutable data and which do not try to solve anything else. They just hold data!

How we define records?

Record is defined with its name and state description (both, together, are called a representation) and when the record is compiled, a fully-fledged API, that matches the representation of a given record, is generated for you, out of the box, by compiler. Record can, optionally, have a body as well.

Q: what is state description?
A: that’s just comma separated list of components of the record. Later, when the record is compiled, compiler uses this state description to generate a public constructor with identical signature;

Q: yikes! what do you mean in components?
A: it’s parameters, which you define right after record name (and before {curly braces}), and for each of which, private final field and special accessor method will be generated by the compiler (more on this a bit later). Think of it as the list of parameters of the constructor that you define for your record (because this list of components is later used, as I’ve already said, to generate corresponding public constructor).

Maybe introducing the terminology of few semantically overlapping words, which weren’t thus far in the vocabulary of Java, is a bit confusing, and I hope I’ve clarified this mist.

[access-modifier] record ([params]){
//optional body
}

and that’s it — you’ve got a fully-fledged, complete, working record.

Now, have a look at what you acquire, out of the box, by just defining your record:

  • private final fields for each component, each with the same type and the same identifier (i.e. variable) name as their respective components have;
  • public accessor methods, for each field, that are named exactly as their corresponding fields, and that return the types of those fields.
    Note here, that we’re not talking about traditional getters, which, by convention, have camelCase names. accessors, were decided to stick with different naming convention — same name as field is named;
  • A public constructor, whose signature is the same as the record’s state description, and which initialises each field from the corresponding argument;
  • Implementations of equals(Object obj) and hashCode() that say two records are equal if they are of the same type and contain the same state; and
  • toString() implementation, that includes the string representation of all the components of the given record (with their names).

So, by defining just:

public record Person(String fullName, int age) {}

You effectively get:

public class Person {

private final String fullName;
private final int age;
public Person(String fullName, int age) {
this.fullName = fullName;
this.age = age;
}
public String fullName() {
return this.fullName;
}
public int age() {
return this.age;
}
//implementation of hashCode() //implementation of equals(Object obj) //implementation of toString()}

You see the difference?

and note!, that I was reluctant to include the implementations for the methods which I’ve included as comments.. it would have been way larger than this, still very verbose and large code.

In other words, you can say, that the record is a shallowly immutable, data aggregation class, sole and only purpose of which, is to store (and maybe help in transferring) the data. Once it’s instantiated, its instance fields can’t be reinitialized.

Break for a moment here..

..and make sure you realise all I wrote above.

If I were you, I would have read above section once again.. maybe.. :)

Let’s continue..

Apart from the syntax of the record, few new conceptual aspects (that I’ve demystified, explaining them as a vocabulary) and the semantics of the records (that mandates shallowly immutable data aggregator type) which they get during compilation, records work like normal classes do. They can:

  • be declared top level or nested;
  • be generic;
  • implement interfaces;
  • define static fields (these can also be declared), static initializers, constructors (see the special note below, on this), instance methods, and nested types;
  • be annotated.

However, there are plenty of things they cannot do!

Compile time restrictions:

  • records implicitly extend java.lang.Record (like enums extend java.lang.Enum).
    When the record is compiled, compiler generates this extension — hence, it cannot extend any other class (including other record). Java does not support multiple inheritance;
  • records cannot declare or define instance fields, other than whatever is generated based on records state description.

Above two points ensure and guarantee, that the only state description defines record’s representation;

  • records are implicitly final, (when record gets compiled, compiler creates public final class YourRecordName extends java.lang.Record out of it) and hence, it cannot be abstract.

This restriction emphasizes, once again, that the API of a record is defined solely by its state description, and it cannot be enhanced by subclassing the record.

Canonical and non-canonical constructors in the body of record

Constructors, defined in the body of Java records, work in a bit peculiar manner.

Canonical Constructors:
You can define canonical constructor (one that matches the signature of record’s state description) in the record body, as, for example:

public record MyRecord(Type1 x, Type2 y) {

public(Type1 x, Type2 y) {
}
}

or! you can avoid the list of the parameters completely:

public record MyRecord(Type1 t, Type2 t) {

public MyRecord {
}
}

and this will still be compiled to the canonical constructor, with all the parameters that state description defines.

Note, that if you explicitly define canonical constructor in the record body:

  1. order and the names of constructor parameters and state description’s components must match (if the canonical constructor is defined with parameters);
  2. you must initialise all the record’s components (state description parameters), disregarding of whether canonical constructor is defined with or without parameters.

Non-canonical constructors:
Interestingly, though, defining non-canonical constructors, in the record’s body, is also allowed, but with certain restrictions.

If you define a non-canonical constructor, you must make sure it delegates to another constructor (with this([..]) call).. and if that “another constructor” is also non-canonical, then it also, in turn, has to delegate to another constructor.. and this chain goes on, until finally the lastly delegated non-canonical constructor delegates to the canonical constructor. If this doesn’t happen, you have a compile time error.

Finally, remember, that any behaviour (methods) that are automatically derived (generated) from the state description, can also be defined explicitly; but you will risk, in this case, to implement the things yourself, that were already taken very thorough and detailed care. Manual/custom implementation of accessors, equals() or hashCode(), may undermine the semantic of the record, that otherwise is “promised” to be delivered.

Congratulations! you’ve just completed a very detailed and a deep-level tutorial on a new, very important and cool topic — Java records.

Switch Expressions (Standard) (JEP 361)

Relax, nothing new here.

I have covered, quite thoroughly, first preview of the Switch Expressions, in my Java 12 - New Language Features article, which I later followed up with an overview of the second preview (update), that was targeted for the newer Java 13 release, in my Java 13 - New Language Features article.

What happened to the Switch Expressions in the Java 14?

They were standardised, without any further amendments since Java 13.

That’s it. Switch Expression (JEP 361) is now a fully standardized part of Java language.

Text Blocks (Second Preview) (JEP 368)

After we’ve covered, in detail, the first preview of Text Blocks, in my Java 13 - New Language Features article, and as you now know where to refer, if you haven’t checked it yet, let me directly cut to the chase and get to the update — what is new in the JEP 368, the second preview of text blocks; what, namely, the second preview added to already existing preview feature — text blocks.

Based on the feedback predecessor JEP (first preview) received from engineers and developers, the second preview of Text Blocks, that is targeted for the Java 14, added two new escape sequences in the Text Blocks:

  1. \<line-terminator> and
  2. \s

\<line-terminator>

\<line-terminator> — this is a new escape sequence, that we can use exclusively in text blocks, to forcefully suppresses a new-line character. Note, that if you’re using some IDE or a text editor, line-terminator can be inserted by just pressing <Enter> and moving cursor to the next line. You just usually do not see special control characters: CRLF (on Windows) or LF (on Linux).

What is this for?
— as mentioned, for suppressing newline character.

Imagine you have a text block, which should contain one long line; however, because of the fact, that your screen is not infinitely wide, you better break that long one line, down, into two lines, in your source code, while keeping the real text (as it’ll be compiled) to one line.
Pay attention! the raw text, real and genuine data must stay one liner, but merely for your convenience, you want to split that long line and have two (or more) lines, in your text editor only!, as the width of your screen is limited, and it’s inconvenient to have a long width’s scrollbar.
You want to code your long one-liner text as two, three, or more lines, but the raw data (text) you wish to have, must be one line. Either you store it in the file or print on the console output, it must be one line.

That’s where \<line-separator> comes into play. You break up the string literal, in your code, with this escape sequence, but in reality, that’s just a visual comfort for you — the text itself still does not include a newline character, it’s still one long line.

The string text in this code:

String text = """
I am Giorgi Tsiklauri. I really like \
coding, teaching, and everything \
that has to do with computers. I constantly learn \
and I have a big interest in data structures, \
algorithms, cyber security, network engineering, \
climate and astrophysics.
"""

does not contain newline control characters, and if this is printed, written in a file, sent through the network, or displayed by any other means, it will be one huge line.

This is a very useful new addition to the string blocks.

Note, that traditional “string literals”, do not allow the source-code level mechanism to break the text into more than one line while keeping the real raw data — one lines.

\s

\s — this escape sequence simply translates into a single space (U+0020 or \u0020 in Java syntax).

The point of this escape sequence is to preserve explicitly coded spaces, when you need them, as, by default, the compiler discards them.

For example, the result of the code:

String text = """
Beethoven's 9th Symphony is transcendental!
""";
System.out.print(text);

will be:

Beethoven's 9th Symphony is transcendental!

even though, in the source code, I’ve appended 5 whitespaces. They are discarded, as the compiler ignores them.

If you, however, wish to preserve spaces, you can use \s, and note, that this escape sequence will work in both - traditional string literals and string blocks.

So, either you’ll code up:

String text = 
"""
Beethoven's 9th Symphony is transcendental! \s
""";

or

String text = "Beethoven's 9th Symphony is transcendental!    \s";

the text will be compiled (and hence — used anywhere you use it) as:

Beethoven's 9th Symphony is transcendental!......

and I’m denoting spaces with . symbols above. Note, that final \s is also included as a space.

That is it for this post.

Thank you for your attention. Follow my profile for more posts about Java, JVM, Algorithms, and Data Structures.

--

--

Giorgi Tsiklauri
Javarevisited

Software engineer, architect, lecturer; long while w/ computers and music; interested in software design, computer networks, composition, security and privacy.