OCP Oracle Certified Professional Java SE 17 Developer Study Guide. Jeanne Boyarsky

OCP Oracle Certified Professional Java SE 17 Developer Study Guide - Jeanne Boyarsky


Скачать книгу
you just need to know that Integer inherits from Number for now. You'll see them a lot in this book!

      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); }

      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); }

      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:

      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.

      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


Скачать книгу