问题
Consider the following set of expressions:
class T {{
/*1*/ super.toString(); // direct
/*2*/ T.super.toString(); // synthetic
Supplier<?> s;
/*3*/ s = super::toString; // synthetic
/*4*/ s = T.super::toString; // synthetic
}}
Which gives the following result:
class T {
T();
0 aload_0 [this]
1 invokespecial java.lang.Object() [8]
4 aload_0 [this]
5 invokespecial java.lang.Object.toString() : java.lang.String [10]
8 pop // ^-- direct
9 aload_0 [this]
10 invokestatic T.access$0(T) : java.lang.String [14]
13 pop // ^-- synthetic
14 aload_0 [this]
15 invokedynamic 0 get(T) : java.util.function.Supplier [21]
20 astore_1 [s] // ^-- methodref to synthetic
21 aload_0 [this]
22 invokedynamic 1 get(T) : java.util.function.Supplier [22]
27 astore_1 // ^-- methodref to synthetic
28 return
static synthetic java.lang.String access$0(T arg0);
0 aload_0 [arg0]
1 invokespecial java.lang.Object.toString() : java.lang.String [10]
4 areturn
Bootstrap methods:
0 : # 40 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:...
#43 invokestatic T.access$0:(LT;)Ljava/lang/String;
1 : # 40 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:...
#46 invokestatic T.access$0:(LT;)Ljava/lang/String;
}
Why java code lines /*2*/
, /*3*/
and /*4*/
produce and use a synthetic accessor method access$0
? I would expect the line /*2*/
and bootstrap methods for lines /*3*/
and /*4*/
to also use invokespecial
as the line /*1*/
does.
Especially when the method Object::toString
is accessible directly from the relevant scope, e.g. the following method reference doesn't wrap a call to a synthetic accessor method:
class F {{
Function<Object, ?> f = Object::toString; // direct
}}
However, there is a difference:
class O {{
super.toString(); // invokespecial -> "className@hashCode"
O.super.toString(); // invokespecial -> "className@hashCode"
Supplier<?> s;
s = super::toString; // invokespecial -> "className@hashCode"
s = O.super::toString; // invokespecial -> "className@hashCode"
Function<Object, ?> f = Object::toString;
f.apply(O.super); // invokeinterface -> "override"
}
public String toString() {return "override";}
}
Which brings another question: Is there a way how to bypass an override in ((Function<Object, ?> Object::toString)::apply
?
回答1:
An invocation of the form super.method()
allows to bypass an overriding method()
in the same class, invoking the most specific method()
of the super class hierarchy. Since, on the byte code level, only the declaring class itself can ignore its own overriding method (and potential overriding methods of subclasses), a synthetic accessor method will be generated if this kind of invocation should be performed by a different (but conceptionally entitled) class, like one of its inner classes, using the form Outer.super.method(...)
, or a synthetic class generated for a method reference.
Note that even if a class doesn't override the invoked method and it seems to make no difference, the invocation has to be compiled this way as there could be subclasses at runtime overriding the method and then, it will make a difference.
It's interesting that the same thing happens when using T.super.method()
when T
actually isn't an outer class but the class containing the statement. In that case, the helper method isn't really necessary, but it seems that the compiler implements all invocations of the form identifier.super.method(...)
uniformly.
As a side note, Oracle's JRE is capable of circumventing this byte code restriction when generating classes for lambda expressions/method references, thus, the accessor methods are not needed for method references of the super::methodName
kind, which can be shown as follows:
import java.lang.invoke.*;
import java.util.function.Supplier;
public class LambdaSuper {
public static void main(String[] args) throws Throwable {
MethodHandles.Lookup l=MethodHandles.lookup();
MethodType mt=MethodType.methodType(String.class);
MethodHandle target=l.findSpecial(Object.class, "toString", mt, LambdaSuper.class);
Supplier<String> s=(Supplier)LambdaMetafactory.metafactory(l, "get",
MethodType.methodType(Supplier.class, LambdaSuper.class),
mt.generic(), target, mt).getTarget().invokeExact(new LambdaSuper());
System.out.println(s.get());
}
@Override
public String toString() {
return "overridden method";
}
}
The generated Supplier
will return something alike LambdaSuper@6b884d57
showing that it invoked the overridden Object.toString()
method rather than the overriding LambdaSuper.toString()
. It seems that the compiler vendors are careful regarding what to expect from the JRE capabilities and, unfortunately, this part seems to be a bit underspecified.
Still, for real inner class scenarios, they are required.
回答2:
Holger already explained why it is happening — super
reference is restricted to the immediate child class only. Here's just a more verbose version of what's really happening there:
Call to an enclosing type's super class' method
class T {
class U {
class V {{
/*2*/ T.super.toString();
}}
}
}
It generates a chain of synthetic accessor methods:
class T {
static synthetic String access$0(T t) { // executing accessor
return t.super.toString(); // only for the refered outer class
}
class U { // new U(T.this)
static synthetic T access$0(U u) { // relaying accessor
return T.this; // for every intermediate outer class
}
class V {{ // new V(U.this)
T.access$0(U.access$0(U.this)); // T.access$0(T.this)
}}
}
}
When T
is the immediately enclosing class, i.e. there are no intermediate outer classes, only the "executing" accessor is generated in class T
(i.e. in itself, which seems to be unnecessary).
N.B.: The accessor chain is generated by Eclipse, but not by OpenJDK, see bellow.
Method reference to own super class' method
class T {
class U {
class V {{
Supplier<?> s;
/*3*/ s = super::toString;
}}
}
}
This generates a synthetic accessor method and a bootstrap method delegating to it:
class T {
class U {
class V {
static synthetic String access$0(V v) {
return v.super.toString();
}
dynamic bootstrap Supplier get(V v) { // methodref
return () -> V.access$0(v); // toString() adapted to get()
}
{
get(V.this);
}
}
}
}
It's a singular case similar to the previous one, since super::toString
is here equivalent to V.super::toString
, so the synthetic accessor is generated in the class V
itself. A new element here is the bootstrap method for adapting Object::toString
to Supplier::get
.
N.B.: Here only OracleJDK is "smart" enough (as Holger pointed out) to avoid the synthetic accessor by placing the super
call directly into the method reference adapter.
Method reference to an enclosing type's super class' method
class T {
class U {
class V {{
Supplier<?> s;
/*4*/ s = T.super::toString;
}}
}
}
As you can expect, this is a combination of the two previous cases:
class T {
static synthetic String access$0(T t) { // executing accessor
return t.super.toString(); // only for the refered outer class
}
class U { // new U(T.this)
static synthetic T access$0(U u) { // relaying accessor
return T.this; // for every intermediate outer class
}
class V { // new V(U.this)
dynamic bootstrap Supplier get(T t) { // methodref
return () -> T.access$0(t); // toString() adapted to get()
}
{
get(U.access$0(U.this)); // get(T.this)
}
}
}
}
Nothing really new here, just note that inner class always receives only the instance of the immediate outer class, so in the class V
, using T.this
it either might go through the whole chain of intermediate synthetic accessor methods, e.g. U.access$0(V.U_this)
(as in Eclipse), or take the advantage of package visibility of these synthetic fields (that reference outer.this
) and translate T.this
to V.U_this.T_this
(as in OpenJDK).
N.B.: The above translations are as per Eclipse compiler. OpenJDK differs in generating instance synthetic lambda methods for method references, instead of static synthetic accessor methods as Eclipse does, and also avoids the accessor chain, so in the last case OpenJDK emits:
class T {
static synthetic String access$0(T t) { // executing accessor
return t.super.toString(); // only for the refered outer class
}
class U { // new U(T.this)
class V { // new V(U.this)
instance synthetic Object lambda$0() {
return T.access$0(V.U_this.T_this); // direct relay
}
dynamic bootstrap Supplier get(V v) { // methodref
return () -> V.lambda$0(v); // lambda$0() adapted to get()
}
{
get(V.this);
}
}
}
}
To sum up, it's quite dependent on the compiler vendor.
来源:https://stackoverflow.com/questions/34675012/why-t-super-tostring-and-supertostring-use-a-synthetic-accessor-method