benf.org :  other :  cfr :  instanceof pattern

Instance of pattern match

As of Java 14, the 'instance of' pattern match has entered preview state. JEP305

This allows you to roll an instanceof check and a cast together, so

if (foo instanceof String) {
   String s = (String)foo;
   System.out.println(s.length());
}

can be written as

if (foo instanceof String s) {
   System.out.println(s.length());
}

... flow typing for Java, and CFR (mostly) handles it!

So what does it generate?

public class InstanceOfPatternTest2 {
    public static void test(Object obj) {
        if (obj instanceof String s){  
           System.out.println(s.length());
        }
    }
}

will generate the following bytecode...

  public static void test(java.lang.Object);
    Code:
       0: aload_0
       1: astore_2
       2: aload_2
       3: instanceof    #7                  // class java/lang/String
       6: ifeq          32
       9: aload_2
      10: checkcast     #7                  // class java/lang/String
      13: dup
      14: astore_1
      15: aload_2
      16: checkcast     #7                  // class java/lang/String
      19: if_acmpne     32
      22: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      25: aload_1
      26: invokevirtual #15                 // Method java/lang/String.length:()I
      29: invokevirtual #19                 // Method java/io/PrintStream.println:(I)V
      32: return

If we use CFR 0.149 with (using --instanceofpattern false), we can see the desugared java.

> java -jar cfr-0.149.jar InstanceOfPatternTest2.class --instanceofpattern false
/*
 * Decompiled with CFR 0.149.
 */
package org.benf.cfr.tests;

public class InstanceOfPatternTest2 {
    public static void test(Object obj) {
        String s;
        Object object = obj;
        if (object instanceof String && (s = (String)object) == (String)object) {
            System.out.println(s.length());
        }
    }
}

There are two interesting things here

I suspect the pointless comparison is purely there to create some sort of legitimate predicate - i.e. this is desugared fairly early in javac.... I can't think of another legitimate reason for it (unless there's a JVM out there in which reference assignment does not imply reference equality!

The temporary variable is weirder - again, I can't really see any benefit to it, and it just pads the bytecode...

But we can recover it...

> java -jar cfr-0.149.jar InstanceOfPatternTest2.class
/*
 * Decompiled with CFR 0.149.
 */
package org.benf.cfr.tests;

public class InstanceOfPatternTest2 {
    public static void test(Object obj) {
        if (obj instanceof String s) {
            System.out.println(s.length());
        }
    }
}

More complex.

Because instanceof patterns are embedded into if expressions, we can combine them in any conditional way, and as long as they're 'definitely assigned', they will be in scope in the applicable branch of the conditional.

We can make use of this with negated conditionals so the instanceof match is only in scope in the 'else' branch.....

> java -jar cfr-0.149.jar InstanceOfPatternTest5.class
/*
 * Decompiled with CFR 0.149.
 */
package org.benf.cfr.tests;

public class InstanceOfPatternTest5 {
    public int s;
    public int i;

    public void test(boolean bool, Object obj) {
        System.out.println(this.s);
        System.out.println(this.i);
        if (!(obj instanceof String s)) {
            System.out.println(this.s);
            System.out.println(this.i);
        } else {
            System.out.println(s.length());
            System.out.println(this.i);
        }
    }
}

Silly

Personal opinion time - I'm not sure allowing these inside something as flexible as a condition was the best idea - rather than using something more constrained like a switch statement (as the JEP originally proposed). Because we have the full power of an if statement, we can do .... interesting .... things.

Consider this, totally sensible code, inside a class with a member 's'.

    public static void test(boolean bool, Object obj) {
        if (bool && obj instanceof String s){   
            System.out.println(s); // <--- instance of variable. 
        } else {
            System.out.println(s); // <--- class member.
        }
    }

Simple, right? We can see that this is a nice positive match....

But one quick application of DeMorgan's law later.....

    public static void test(boolean bool, Object obj) {
        if (!(!bool || !(obj instanceof String s))){
            System.out.println(s); // <--- instance of variable. 
        } else {
            System.out.println(s); // <--- class member.
        }
    }

Ok, you can always write stupid conditionals - but not ones that generated variable scopes!

and the less said about using the assigned variables inside the conditional, the better........

Dangerous

I really hope the spec as-is (02/2020) doesn't make it into production code. There are somee *really* rough edges around definite reachability which have been found

My personal favourite is subtly different, but just as awful:

    public static void test(Object obj) {
        if (!(obj instanceof String s)) {
            throw new IllegalStateException();
        }
        System.out.println(s);
    }

and

    public static void test2(Object obj) {
        if (!(obj instanceof String s)) {
            if(true) {
                throw new IllegalStateException();
            }
        }
        System.out.println(s);
    }

.... mean different things! (See InstanceOfPatternTest10.java)


Last updated 02/2020