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:
Object
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.
Variables declared inside the class but outside the body of the method are called instance variables. It is called instance variable because its value is instance specific and is not shared among instances. Variables declared inside the body of a method are called local variables.
In Java, var can be used only where type inference is desired; it cannot be used where a type is declared explicitly. If val were added, it too could be used only where type inference is used. The use of var or val in Java could not be used to control immutability if the type were declared explicitly.
Things an object does are its methods (behavior). Methods can use instance variables so that objects of the same type can behave differently. A method can have parameters, which means you can pass one or more values in to the method.
You can't. Variables defined inside a method are local to that method. If you want to share variables between methods, then you'll need to specify them as member variables of the class. Alternatively, you can pass them from one method to another as arguments (this isn't always applicable).
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
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With