Why do Object and var variables behave differently?
Asked Answered
M

2

7

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.

Meggs answered 11/12, 2019 at 9:23 Comment(5)
It is because the type of o1 is explicitly declared as Object while the type of o2 is the same as the RHS, which extends Object and has the extra method, so you can call it.Saeger
@Saeger I like your explanation 'RHS' and this can be shown more detailed like this: new Object() { String getSomething() { return "AAA"; } }.getSomething(); But I would love to see where is it clearly documented.Meggs
You have completely mangled what I said in your answer, so that it doesn't amount to what I said at all. Nor is your misinterpretation correct as to what the compiler actually does. What it does is assign the type of o2 to be the same as the type of the RHS, that being exactly what var does. Which is documented in the JLS.Saeger
@Saeger sorry, indeed it made expression I credited you with something which you did not said, and when I made that demo code snippet I mean it conceptually, not literally. In any case, I updated the question again.Meggs
See also this Q&A.Crissie
A
5

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
Annabelle answered 11/12, 2019 at 9:29 Comment(6)
I would love more technical answer, both are inner anonymous types. My question was why compiler does not complain for o2 (I know for sure why it complains for o1)Meggs
@BaratSahdzijeu: I don't quite understand: the type of o1 is Object and Object doesn't have a getSomething method. That's about as technical as it gets ... The type of o2 is not Object, but the unnamed type you create at its initialization ... The difference is in the type of the variable not in the type of the value assigned to it!Annabelle
for sure I understand that behavior caused Object variable type v.s. var type name. Can you explain why in that case methods comparison for both objects identical, and btw getSomething is not among them?Meggs
@BaratSahdzijeu: when you call getClass() then you look at the actual type of the object referenced by the variables. That is not dependent on the type of the variable that holds the reference.Annabelle
any way to get getSomething method evidence via Reflection API ? Your comment explained why true is returned, but not why getSomething is not on the returned list. I assume the type is not Object, but rather some Object subclass: e.g. Test$1 extends Object, Test$2 extends ObjectMeggs
@Barat The method is retrievable via reflection. In your question you call #getMethods() which only returns the public methods. If you were to use #getDeclaredMethods() then you'd see the method you're looking for. The documentation goes into more detail.Offence
D
6

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();
Dehnel answered 11/12, 2019 at 9:33 Comment(3)
I'd suggest going to the JLS for the exact specification, JEP is the process to get it included in the JLS and sometimes the phrasing can differ ...Annabelle
@JoachimSauer Would surely do that in some time to spare. For now, quoted what I remembered reading through.Dehnel
@JoachimSauer I might have just been too late to edit(I can see the JLS quoted in your answer), but I quoted the line which compliments your answer and I hope it is worth keeping as a note for future readers.Dehnel
A
5

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
Annabelle answered 11/12, 2019 at 9:29 Comment(6)
I would love more technical answer, both are inner anonymous types. My question was why compiler does not complain for o2 (I know for sure why it complains for o1)Meggs
@BaratSahdzijeu: I don't quite understand: the type of o1 is Object and Object doesn't have a getSomething method. That's about as technical as it gets ... The type of o2 is not Object, but the unnamed type you create at its initialization ... The difference is in the type of the variable not in the type of the value assigned to it!Annabelle
for sure I understand that behavior caused Object variable type v.s. var type name. Can you explain why in that case methods comparison for both objects identical, and btw getSomething is not among them?Meggs
@BaratSahdzijeu: when you call getClass() then you look at the actual type of the object referenced by the variables. That is not dependent on the type of the variable that holds the reference.Annabelle
any way to get getSomething method evidence via Reflection API ? Your comment explained why true is returned, but not why getSomething is not on the returned list. I assume the type is not Object, but rather some Object subclass: e.g. Test$1 extends Object, Test$2 extends ObjectMeggs
@Barat The method is retrievable via reflection. In your question you call #getMethods() which only returns the public methods. If you were to use #getDeclaredMethods() then you'd see the method you're looking for. The documentation goes into more detail.Offence

© 2022 - 2024 — McMap. All rights reserved.