Book Image

Java Coding Problems - Second Edition

By : Anghel Leonard
Book Image

Java Coding Problems - Second Edition

By: Anghel Leonard

Overview of this book

The super-fast evolution of the JDK between versions 12 and 21 has made the learning curve of modern Java steeper, and increased the time needed to learn it. This book will make your learning journey quicker and increase your willingness to try Java’s new features by explaining the correct practices and decisions related to complexity, performance, readability, and more. Java Coding Problems takes you through Java’s latest features but doesn’t always advocate the use of new solutions — instead, it focuses on revealing the trade-offs involved in deciding what the best solution is for a certain problem. There are more than two hundred brand new and carefully selected problems in this second edition, chosen to highlight and cover the core everyday challenges of a Java programmer. Apart from providing a comprehensive compendium of problem solutions based on real-world examples, this book will also give you the confidence to answer questions relating to matching particular streams and methods to various problems. By the end of this book you will have gained a strong understanding of Java’s new features and have the confidence to develop and choose the right solutions to your problems.
Table of Contents (16 chapters)
1
Text Blocks, Locales, Numbers, and Math
Free Chapter
2
Objects, Immutability, Switch Expressions, and Pattern Matching
14
Other Books You May Enjoy
15
Index

59. Handling the scope of a binding variable in type patterns for instanceof

From Problem 57, we know the headlines of scoping the binding variables in pattern matching. Moreover, we know from the previous problem that in the type pattern for instanceof, we have a single binding variable. It is time to see some practical examples, so let’s quickly crop this snippet from the previous problem:

if (o instanceof File file) {
  return "Saving a file of size: " 
    + String.format("%,d bytes", file.length());
}
// 'file' is out of scope here

In this snippet, the file binding variable is visible in the if-then block. Once the block is closed, the file binding variable is out of scope. But, thanks to flow scoping, a binding variable can be used in the if statement that has introduced it to define a so-called guarded pattern. Here it is:

// 'file' is created ONLY if 'instanceof' returns true
if (o instanceof File file
    // this is evaluated ONLY if 'file' was created
    && file.length() > 0 && file.length() < 1000) {
  return "Saving a file of size: " 
    + String.format("%,d bytes", file.length());
}
// another example
if (o instanceof Path path
     && Files.size(path) > 0 && Files.size(path) < 1000) {
  return "Saving a file of size: " 
    + String.format("%,d bytes", Files.size(path));
}

The conditional part that starts with the && short-circuit operator is evaluated by the compiler only if the instanceof operator is evaluated to true. This means that you cannot use the || operator instead of &&. For instance, is not logical to write this:

// this will not compile
if (o instanceof Path path
  || Files.size(path) > 0 && Files.size(path) < 1000) {...}

On the other hand, this is perfectly acceptable:

if (o instanceof Path path
  && (Files.size(path) > 0 || Files.size(path) < 1000)) {...}

We can also extend the scope of the binding variable as follows:

if (!(o instanceof String str)) {
  // str is not available here
  return "I cannot save the given object";
} else {
  return "Saving a string of size: " 
    + String.format("%,d bytes", str.length());
}

Since we negate the if-then statement, the str binding variable is available in the else branch. Following this logic, we can use early returns as well:

public int getStringLength(Object o) { 
  if (!(o instanceof String str)) {
    return 0;
  }
  return str.length();
}

Thanks to flow scoping, the compiler can set up strict boundaries for the scope of binding variables. For instance, in the following code, there is no risk of overlapping even if we keep using the same name for the binding variables:

private String strNumber(Object o) {
 if (o instanceof Integer nr) {
   return String.valueOf(nr.intValue());
 } else if (o instanceof Long nr) {
   return String.valueOf(nr.longValue());
 } else {
   // nr is out of scope here
   return "Probably a float number";
 }
}

Here, each nr binding variable has a scope that covers only its own branch. No overlapping, no conflicts! However, using the same name for the multiple binding variables can be a little bit confusing, so it is better to avoid it. For instance, we can use intNr and longNr instead of simple nr.

Another confusing scenario that is highly recommended to be avoided implies binding variables that hide fields. Check out this code:

private final String str
  = "   I am a string with leading and trailing spaces     ";
public String convert(Object o) {
  // local variable (binding variable) hides a field
  if (o instanceof String str) { 
    return str.strip(); // refers to binding variable, str
  } else {
    return str.strip(); // refers to field, str
  } 
}

So, using the same name for binding variables (this is true for any local variable as well) and fields is a bad practice that should be avoided.

In JDK 14/15, we cannot reassign binding variables because they are declared final by default. However, JDK 16+ solved the asymmetries that may occur between local and binding variables by removing the final modifier. So, starting with JDK 16+, we can reassign binding variables as in the following snippet:

String dummy = "";
private int getLength(Object o) { 
  if(o instanceof String str) {
      str = dummy; // reassigning binding variable
      // returns the length of 'dummy' not the passed 'str'
      return str.length(); 
  }
  return 0;
}

Even if this is possible, it is highly recommended to avoid such code smells and keep the world clean and happy by not re-assigning your binding variables.