Why do Object and var variables behave differently?

211 Views Asked by At

Can someone explain the behavior of o2? Is it due to compiler optimization? Is it documented somewhere in the JLS?

public class Test {
    public static void main(String[] args) {
        Object o1 = new Object() {
            String getSomething() {
                return "AAA";
            }
        };
        // o1.getSomething(); // FAILS
        String methods1 = Arrays.toString(o1.getClass().getMethods());        
        var o2 = new Object() {
            String getSomething() {
                return "AAA";
            }
        };        
        o2.getSomething(); // OK        
        String methods2 = Arrays.toString(o2.getClass().getMethods());        
        System.out.println(methods1.equals(methods2));
    }    
}

The output produced is

true

[UPDATE]

After some productive and useful discussion I think I can understand the behavior (please post comments if my assumption is wrong).

First off, thanks to @user207421 who explained that the Java compiler treats the type of o2 the same as the RHS, which:

  • extends Object
  • has the getSomething method

Then thanks to @Joachim Sauer who pointed to the proper place in the JLS.

Some more related JLS quotes:

The type of the local variable is the upward projection of T with respect to all synthetic type variables mentioned by T (§4.10.5).

Upward projection is applied to the type of the initializer when determining the type of the variable. If the type of the initializer contains capture variables, this projection maps the type of the initializer to a supertype that does not contain capture variables.

While it would be possible to allow the type of the variable to mention capture variables, by projecting them away we enforce an attractive invariant that the scope of a capture variable is never larger than the statement containing the expression whose type is captured. Informally, capture variables cannot "leak" into subsequent statements.

Question: can we say that "capture variables" refers to getSomething() too in the context of the question?

And finally, thanks to @Slaw for pointing out that getSomething was declared package private so was not returned by getMethods.

Any comments/corrections appreciated.

2

There are 2 best solutions below

6
Joachim Sauer On BEST ANSWER

Object has no method getSomething. And since o1 is of type Object the compiler won't allow you to call o1.getSomething.

In the case of o2 the type of the variable is the anonymous inner type that you created during initialization. That type has a getSomething method, so the compiler will allow you to call it.

Interestingly this is something that you can't directly do by having a named type. There's no type name that you use in the declaration of o2 to get the same effect, because the type is anonymous.

It is defined in JLS 14.4.1 Local Variable Declarators and Types. Specifically this part:

If LocalVariableType is var, then let T be the type of the initializer expression when treated as if it did not appear in an assignment context, and were thus a standalone expression (§15.2).

There's even an example that shows that below:

var d = new Object() {};  // d has the type of the anonymous class
3
Naman On

The section represented as Non-Denotable Types in the JEP 286: Local-Variable Type Inference states:

Anonymous class types cannot be named, but they're easily understood—they're just classes. Allowing variables to have anonymous class types introduces a useful shorthand for declaring a singleton instance of a local class. We allow them.

Hence the method that you invoke using the var is allowed to compile considering the class instance is created and inferred as an anonymous class further allowing the method to be invoked.

The Local Variable Declarators and Type section of the specification mentions this as a side note along with the example as well:

var d = new Object() {};  // d has the type of the anonymous class

Note that some variables declared with var cannot be declared with an explicit type, because the type of the variable is not denotable.

On the other hand, with the first instance what you're trying to perform looks like Invoking a method of an anonymous class, which fails since the type of o1 is inferred to be Object and that further doesn't have a method called getSomething. While if you were to invoke the method getSomething and fix the compilation there, you could have used

Object o1 = new Object() {
  String getSomething() {
    System.out.println("something happened");
    return "AAA";
  }
}.getSomething();