Search code examples
javajava-16

Why is an identifier required for instanceof pattern matching?


In Java's instanceof pattern matching, an additional identifier is used to narrow the type:

Object o = "test";
if (o instanceof String s) {
    System.out.println(s.length()); // s is of type String
}

Why is that necessary? The JEP doesn't seem to justify it, but I'm sure there's a reason. What's undesirable about the following?

Object o = "test";
if (o instanceof String) {
    System.out.println(o.length());
}

TypeScript and Kotlin do similar things without another identifier.

I'm guessing it's a backwards-compatibility thing - Java's quirk's usually are - but I can't think of an example which would demonstrate that.

The only thing I can think is that someone may have written something similar to that second example pre-pattern matching and relies on it to not compile, but that seems like a weak justification.


Solution

  • If Java introduced something like a “smart instanceof”, we could argue that this feature could have worked without introducing a new variable.

    But that’s not what has been introduced. The new feature is Pattern Matching, a far bigger concept, though only implemented in the simplest form in the first step. This is also a new integration approach, instead of working decades on the big feature, smaller features are continuously added to Java while maintaining the big vision.

    JDK-8260244 describes one of the next steps, which would allow something like

    (given)

    record Point(int x, int y) {}
    
    void printSum(Object o) {
        if (o instanceof Point(int x, int y)) {
            System.out.println(x+y);
        }
    }
    

    or even

    (given)

    record Point(int x, int y) {}
    enum Color { RED, GREEN, BLUE }
    record ColoredPoint(Point p, Color c) {}
    record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
    
    static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
        if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), var lr)) {
            System.out.println("Upper-left corner: " + x);
        }
    }
    

    Since Pattern Matching includes the creation of (sometimes multiple) new variables, the case of creating only one new variable containing the same reference as an already existing variable is just a corner case. Even what has been implemented so far, covers more cases than that. E.g.

    if(foo.getObject() instanceof List<?> l && l.get(0) instanceof Map<?,?> m
       && m.get("foo") instanceof String s) {
           // use s
    }
    

    It’s also worth noting that the scope of a pattern variable is complex. It would be even more confusing, if the variable existed outside this scope as well, but with a different type.

    So, changing the type of a variable contextually in the presence of instanceof, while tempting, would cause problems (some comments also mentioned method overload selection), while at the same time, wouldn’t fit into the actual vision of the Java language developers.