Why 'T.super.toString()' and 'super::t

2019-04-19 18:44发布

问题:

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.