Search code examples
javaobjectcompiler-errorspattern-matchinginstanceof

instanceof Pattern matching in Java, not compiling


Below is a paragraph taken from 'Java - The Complete Reference' book, 12th edition.

Number myOb = Integer.valueOf(9);
int count = 10; 
if((count < 100) && myOb instanceof Integer iObj) { // is OK 
// myOb is both an Integer and nonnegative, and count is less than 100. 
iObj = count; 
//.. 
}

This fragment compiles because the if block will execute only when both sides of the && are

true. Thus, the use of iObj in the if block is valid. However, a compilation error will result if

you tried to use the & rather than the &&, as shown here:

if((count < 100) & myOb instanceof Integer iObj) { // Error!

In this case, the compiler cannot know whether or not iObj will be in scope in the if block

because the right side of the & will not necessarily be evaluated.

I completely do not understand the last few lines, why would the code even enter the if block if the pattern matching fails, if it succeeds and the first condition is true, then what's wrong with using '&' instead of '&&'.

The code works if I initialise iObj manually inside the if block, but why is pattern matching not doing it? When it's whole idea was to create a reference variable, pointing to the extracted object of mentioned type.

What I get before manual initialization

Compile Error: iObj may not be initialized

What I tried

if((count < 100) & myOb instanceof Integer iObj) {
int iObj = (Integer) myOb;
}

And now it works.

I want to know why, this happens.

I am using JDK 20 in Windows

java version "20.0.1" 2023-04-18
Java(TM) SE Runtime Environment (build 20.0.1+9-29)
Java HotSpot(TM) 64-Bit Server VM (build 20.0.1+9-29, mixed mode, sharing)

Solution

  • The way the quote explains why the second case does not compile is worded extremely strange, maybe even wrong. So here is my way of explaining it.

    The way you have to look at pattern match variables is that they are only declared if they are assigned (in the spec they call this 'introducing' a pattern variable).

    Let's look at an example:

    if(myOb instanceof Integer iObj) {
        // ...
    }
    

    Now inside this if statement, the instanceof must have returned true, so iObj is 'introduced' and therefore declared. This means you can both read from it, and write to it (as it isn't a final variable, note that myOb instanceof final Integer iObj is valid Java).

    Let's now jump to the first case given:

    if((count < 100) && myOb instanceof Integer iObj) {
        // ...
    }
    

    So why does this work? The language designers made sure that if you have a statement in the form of:

    if (exprA && exprB) {
      // ...
    }
    

    that it is equivalent to:

    if (exprA) {
      if (exprB) {
        // ...
      }
    }
    

    This why you have short circuiting in Java and so many other languages. Doing this to the 1st case we get:

    if (count < 100) {
      if (myOb instanceof Integer iObj) {
        // ...
      }
    }
    

    Because the inner if matches the first example I gave, it makes sense that again inside the if block the pattern variable must have been 'introduced' and is therefore assignable.

    So why doesn't this work with & just like with &&? Well, because the language designers decided not to I suppose.

    I do have an argument as to why they decided this. Given a statement like this:

    if (exprA & exprB) {
      // ...
    }
    

    It is essentially the same as:

    boolean tempA = exprA;
    boolean tempB = exprB;
    if (tempA && tempB) {
      // ...
    }
    

    And although we can split the && operator here again, it doesn't matter. In the case you gave, we would get boolean tempB = myOb instanceof Integer iObj. This statement can't 'introduce' a pattern variable because it could be that myOb wasn't an Integer which means that it can't guarantee that iObj would be assigned.

    So even though 'myOb instanceof Integer iObj' in both cases must have returned true for the code in the if block to be run, because the language designers didn't make rules for how pattern variables get introduced by & like they did with &&, the 2nd case in the book won't compile like the first one.

    So why can you use int iObj = ... in the if block with & but not with &&? Well, as we just saw with &, the compiler doesn't consider the pattern variable to be 'introduced' to the block, so you can define it inside, but with && that is illegal because the pattern variable IS introduced, which means you can't redeclare it.