Integer
inherits from Number
for now. You'll see them a lot in this book!
void compareIntegers(Number number) { if(number instanceof Integer) { Integer data = (Integer)number; System.out.print(data.compareTo(5)); } }
The cast is needed since the compareTo()
method is defined on Integer
, but not on Number
.
Code that first checks if a variable is of a particular type and then immediately casts it to that type is extremely common in the Java world. It's so common that the authors of Java decided to implement a shorter syntax for it:
void compareIntegers(Number number) { if(number instanceof Integer data) { System.out.print(data.compareTo(5)); } }
The variable data
in this example is referred to as the pattern variable. Notice that this code also avoids any potential ClassCastException
because the cast operation is executed only if the implicit instanceof
operator returns true
.
Reassigning Pattern Variables
While possible, it is a bad practice to reassign a pattern variable since doing so can lead to ambiguity about what is and is not in scope.
if(number instanceof Integer data) { data = 10; }
The reassignment can be prevented with a final
modifier, but it is better not to reassign the variable at all.
if(number instanceof final Integer data) { data = 10; // DOES NOT COMPILE }
Pattern Variables and Expressions
Pattern matching includes expressions that can be used to filter data out, such as in the following example:
void printIntegersGreaterThan5(Number number) { if(number instanceof Integer data && data.compareTo(5)>0) System.out.print(data); }
We can apply a number of filters, or patterns, so that the if
statement is executed only in specific circumstances. Notice that we're using the pattern variable in an expression in the same line in which it is declared.
Subtypes
The type of the pattern variable must be a subtype of the variable on the left side of the expression. It also cannot be the same type. This rule does not exist for traditional instanceof
operator expressions, though. Consider the following two uses of the instanceof
operator:
Integer value = 123; if(value instanceof Integer) {} if(value instanceof Integer data) {} // DOES NOT COMPILE
While the second line compiles, the last line does not compile because pattern matching requires that the pattern variable type Integer
be a strict subtype of Integer
.
Limitations of Subtype Enforcement
The compiler has some limitations on enforcing pattern matching types when we mix classes and interfaces, which will make more sense after you read Chapter 7, “Beyond Classes.” For example, given the non-final
class Number
and interface List
, this does compile even though they are unrelated:
Number value = 123; if(value instanceof List) {} if(value instanceof List data) {}
Flow Scoping
The compiler applies flow scoping when working with pattern matching. Flow scoping means the variable is only in scope when the compiler can definitively determine its type. Flow scoping is unlike any other type of scoping in that it is not strictly hierarchical like instance, class, or local scoping. It is determined by the compiler based on the branching and flow of the program.
Given this information, can you see why the following does not compile?
void printIntegersOrNumbersGreaterThan5(Number number) { if(number instanceof Integer data || data.compareTo(5)>0) System.out.print(data); }
If the input does not inherit Integer
, the data
variable is undefined. Since the compiler cannot guarantee that data
is an instance of Integer
, data
is not in scope, and the code does not compile.
What about this example?
void printIntegerTwice(Number number) { if (number instanceof Integer data) System.out.print(data.intValue()); System.out.print(data.intValue()); // DOES NOT COMPILE }
Since the input might not have inherited Integer
, data
is no longer in scope after the if
statement. Oh, so you might be thinking that the pattern variable is then only in scope inside the if
statement, right? Well, not exactly! Consider the following example that does compile:
void printOnlyIntegers(Number number) { if (!(number instanceof Integer data)) return; System.out.print(data.intValue()); }
It might surprise you to learn this code does compile. Eek! What is going on here? The method returns if the input does not inherit Integer
. This means that when the last line of the method is reached, the input must inherit Integer
, and therefore data
stays in scope even after the if
statement ends.
Flow Scoping and else Branches
If the last code sample confuses you, don't worry: you're not alone! Another way to think about it is to rewrite the logic to something equivalent that uses an else
statement:
void printOnlyIntegers(Number number) { if (!(number instanceof Integer data)) return; else System.out.print(data.intValue()); }
We can now go one step further and reverse the if
and else
branches by inverting the boolean
expression:
void printOnlyIntegers(Number number) { if (number instanceof Integer data) System.out.print(data.intValue()); else return; }
Our new code is equivalent to our original and better demonstrates how the compiler was able to determine that data
was in scope only when number
is an Integer
.
Make sure you understand the way flow scoping works. In particular, it is possible to use a pattern variable outside of the if
statement, but only when the compiler can definitively determine its type.
Applying switch Statements
What if we have a lot of possible branches or paths for a single value? For example, we might want to print a different message based on the day of the week. We could certainly accomplish this with a combination of seven if
or else
statements, but that tends to create code that is long, difficult to read, and often not fun to maintain:
public void printDayOfWeek(int day) { if(day == 0) System.out.print("Sunday"); else if(day == 1) System.out.print("Monday"); else if(day == 2) System.out.print("Tuesday"); else if(day == 3) System.out.print("Wednesday"); … }
Luckily, Java, along with many other languages, provides a cleaner approach. In this section we present the switch
statement, along with the newer switch
expression for controlling program flow.
The switch Statement
A switch
statement, as shown in Figure 3.3, is a complex decision-making structure in which a single value is evaluated and flow is redirected to the first matching branch, known as a case