Repetitive and error-prone code with instanceof pre java 14
Following is typical example of instanceof operator before java 14.
if (obj instanceof String)
{
String s = (String) obj;
int length = s.length();
}
Three tasks are done here:
- Test (is
obj
a String?) - A conversion (casting
obj
toString
) - The declaration of a new local variable (
s
) so we can use the string value.
This pattern has 3 problems.
- It is tedious: doing both the type test and cast should be unnecessary (what else would you do after an instanceof test?).
- This boilerplate: in particular, the three occurrences of the type
String
obfuscates the more significant logic that follows. - Repetition, repetition of
String
provides opportunities for errors to creep unnoticed into programs.
Java solves these problems by pattern matching. instanceof pattern matching is preview features in Java 14 and 15. Using this same code is written as below, here you will not see the three problems we discussed.
if (obj instanceof String s)
{
int length = s.length();
}
It was basic example of instanceof pattern matching. It simplifies messy operations. We will take 2 more examples to show how it simplifies code.
1. Simplification of equals method implementation
For a class Point, we might implement equals() as follows
class Point
{
int x;
int y;
}
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point other = (Point) o;
return x == other.x
&& y == other.y;
}
Using a pattern match instead, we can combine this into a single expression, eliminating the repetition and simplifying the control flow
public boolean equals(Object o) {
return (o instanceof Point other)
&& x == other.x
&& y == other.y;
}
2. Simplifying if else chain with instanceof pattern matching
Following is the code of if else chain with instanceof before java 14
private static void oldInstanceOf(Object obj)
{
String formatted = "unknown";
if (obj instanceof Integer) {
int i = (Integer) obj;
formatted = String.format("int %d", i);
}
else if (obj instanceof Byte) {
byte b = (Byte) obj;
formatted = String.format("byte %d", b);
}
else if (obj instanceof Long) {
long l = (Long) obj;
formatted = String.format("long %d", l);
}
else if (obj instanceof Double) {
double d = (Double) obj;
formatted = String.format("double %f", d);
}
else if (obj instanceof String) {
String s = (String) obj;
formatted = String.format("String %s", s);
}
System.out.println(formatted);
}
Now below is the code of if else chain using instanceof pattern matching
private static void newInstanceOf(Object obj) {
String formatted = "unknown";
if (obj instanceof Integer i) {
formatted = String.format("int %d", i);
}
else if (obj instanceof Byte b) {
formatted = String.format("byte %d", b);
}
else if (obj instanceof Long l) {
formatted = String.format("long %d", l);
}
else if (obj instanceof Double d) {
formatted = String.format("double %f", d);
}
else if (obj instanceof String s) {
formatted = String.format("String %s", s);
}
System.out.println(formatted);
}
Before going to details of this language feature it is worth understanding what exactly is the pattern matching and important terminologies related to pattern matching in instanceof.
What is Pattern matching?
A pattern is a combination of
- a match predicate that determines if the pattern matches a target
- a set of pattern variables that are conditionally extracted if the pattern matches the target.
A type test pattern consists of
- a predicate that specifies a type
- a single binding variable.
Before java 14 the instanceof operator was taking just a type now it
is extended to take a type test pattern
.
This is an example of an improved instanceof operator
if (obj instanceof String s) {
...
}
In this example the instanceof operator matches the target obj to the type test pattern as follows:
- Check if
obj
is an instance ofString
- If the above check is true then cast
obj
toString
and assigned to the binding variables
.
This is overall pattern matching for instanceof operator.
To understand this feature completely we will need to understand scoping rules for binding variable. Next section we will try to understand scoping rules.
Helpful flow sensitive scoping rules for instanceof binding variable
The scope is one of the best part of this language feature, the binding variable is available only where it is required. It makes code bug free.
To understand the scope of binding variable we will go through 3 examples.
- Basic case
- expression with short circuit && operator
- expression with short circuit || operator
Basic case
Let’s take a look at the basic scenario, using following code example.
if (integer instanceof Number number) {
System.out.println(number.intValue());
} else {
System.out.println("Binding variable number not accessible here");
}
In this example, the instanceof
operator “matches” the target number to the type
test pattern as follows:
- if the number is an instance of
Number
, then it is cast toNumber
and assigned to the binding variablenumber
. - The binding variable is in scope of the true block of the if statement, and not in the false block of the if statement.
expression with short circuit && operator
Now we will take an example of a little complex expression short circuit && operator.
Object obj = "Name";
if (obj instanceof String s && s.length() > 3)
{
System.out.println(s.charAt(1));
}
else
{
System.out.println("s not accessible here");
}
Here in the above example s.length()
will be called only when obj
is instance of String
.
That’s why it makes sense to have s
accessible on right-hand side of the short circuit operator as well.
When both conditions in short circuit && operator are true, which also means s
is
a String
, s
is accessible in if block as well.
expression with short circuit || operator
This is an example with ||
operator, it has compilation error at s.length()
and s.charAt(1)
if (obj instanceof String s || s.length() > 3)
{
System.out.println(s.charAt(1));
}
Here when obj is String, it doesn’t need to evaluate the expression on the right side,
so s not accessible on right side expression (s.length() > 3
).
When obj
is not String
, it doesn’t get cast and then assigned to the binding variable.
The binding variable s
is not in scope on the right-hand side of the ||
operator, nor it is in scope in the true block, and we get compilation error.
At the end
Knowing language features like this helps you get the best java jobs, that’s why to help you I wrote ebook 5 steps to Best Java Jobs. Download this step by step guide for free!
Resources
- https://openjdk.java.net/jeps/375, This is Java enhancement proposal for Pattern Matching for instanceof in JDK15.
- https://github.com/Vipin-Sharma/JDK15Examples, this is link to code examples used in this post.
- https://www.infoq.com/presentations/java-futures-2019/ , Presentation by Java Language Architect Brian Goetz.
- https://cr.openjdk.java.net/~briangoetz/amber/pattern-match.html