benf.org : other : cfr : instanceof pattern |
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!
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
The temporary variable is weirder - again, I can't really see any benefit to it, and it just pads the bytecode...
> 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()); } } }
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); } } }
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........
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 |