Handling the scope of a binding variable in type patterns for instanceof – Objects, Immutability, Switch Expressions, and Pattern Matching

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

From Problem 52, we know the headlines of scoping the binding variables in pattern matching. Moreover, we know from the previous problem that in type pattern for instanceof we have a single binding variable. It is time to see some practical examples, and 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! But, using the same name for the multiple binding variables can be a little bit confusing, so better 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, is highly recommended to avoid such code smells and keep the world clean and happy by not re-assigning your binding variables.

Leave a Reply

Your email address will not be published. Required fields are marked *

© 2024 nickshade Please read our Terms and Privacy Policy. We also use Cookies.