为什么'T.super.toString()'和'super::toString'使用合成访问器方法?

请考虑以下一组表达式:

class T {{
/*1*/   super.toString();      // direct
/*2*/   T.super.toString();    // synthetic
        Supplier<?> s;
/*3*/   s = super::toString;   // synthetic
/*4*/   s = T.super::toString; // synthetic
}}

这给出了以下结果:

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;
}

为什么java代码行,并生成和使用综合访问器方法?我希望行和引导方法用于行,并且也像行一样使用。/*2*//*3*//*4*/access$0/*2*//*3*//*4*/invokespecial/*1*/

特别是当可以直接从相关范围访问该方法时,例如,以下方法引用不会包装对综合访问器方法的调用:Object::toString

class F {{
    Function<Object, ?> f = Object::toString; // direct
}}

但是,一个区别:

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";}
}

这带来了另一个问题:有没有办法绕过覆盖?((Function<Object, ?> Object::toString)::apply


答案 1

对窗体的调用允许绕过同一类中的重写,从而调用超类层次结构中最具体的。由于在字节码级别,只有声明类本身可以忽略其自己的重写方法(以及子类的潜在重写方法),因此,如果这种调用应由不同的(但在概念上授权的)类(如其内部类之一)执行,则将使用形式或为方法引用生成的合成类来生成综合访问器方法。super.method()method()method()Outer.super.method(...)

请注意,即使一个类没有重写被调用的方法并且似乎没有区别,调用也必须以这种方式编译,因为在运行时可能会有子类覆盖该方法,然后,它将有所不同。

有趣的是,当使用 when 实际上不是外部类而是包含语句的类时,也会发生同样的事情。在这种情况下,帮助器方法并不是必需的,但编译器似乎统一实现了窗体的所有调用。T.super.method()Tidentifier.super.method(...)


作为旁注,Oracle的JRE能够在为lambda表达式/方法引用生成类时规避此字节代码限制,因此,该类型的方法引用不需要访问器方法,可以如下所示:super::methodName

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";
    }
}

生成的将返回类似的东西,表明它调用了重写的方法而不是重写的方法。编译器供应商似乎对JRE功能的期望很谨慎,不幸的是,这部分似乎有点被低估了。SupplierLambdaSuper@6b884d57Object.toString()LambdaSuper.toString()

尽管如此,对于真正的内部类方案,它们是必需


答案 2

Holger已经解释了为什么会发生这种情况 - 引用仅限于直接子类。以下是那里真正发生的事情的更详细的版本:super


调用封闭类型的超类'方法

class T {
    class U {
        class V {{
/*2*/       T.super.toString();
        }}
    }
}

它生成一系列合成访问器方法:

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)
        }}
    }
}

什么时候是立即封闭的类,即没有中间的外部类,只有“执行”访问器在类中生成(即本身,这似乎是不必要的)。TT

注意:访问器链是由 Eclipse 生成的,但不是由 OpenJDK 生成的,请参见下面的波纹管。


方法引用自己的超类的方法

class T {
    class U {
        class V {{
            Supplier<?> s;
/*3*/       s = super::toString;
        }}
    }
}

这将生成一个合成访问器方法和一个委托给它的引导方法:

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);
            }
        }
    }
}

这是一个与前一个类似的单例情况,因为这里等效于 ,因此合成访问器是在类本身中生成的。这里的一个新元素是适应 的引导方法。super::toStringV.super::toStringVObject::toStringSupplier::get

注意:这里只有OracleJDK足够“智能”(正如Holger指出的那样),通过将调用直接放入方法参考适配器来避免合成访问器。super


对封闭类型超类的方法引用

class T {
    class U {
        class V {{
            Supplier<?> s;
/*4*/       s = T.super::toString;
        }}
    }
}

如您所料,这是前两种情况的组合:

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)
            }
        }
    }
}

这里没有什么真正的新东西,只需注意内部类始终只接收直接外部类的实例,因此在类中,使用它可能会经历整个中间合成访问器方法链,例如 (如在Eclipse中),或者利用这些合成字段(引用)的包可见性并转换为(如OpenJDK中)。VT.thisU.access$0(V.U_this)outer.thisT.thisV.U_this.T_this


注意:上面的翻译是根据Eclipse编译器进行的。OpenJDK的不同之处在于为方法引用生成实例合成lambda方法,而不是像Eclipse那样生成静态合成访问器方法,并且还避免了访问器链,因此在最后一种情况下,OpenJDK发出:
总而言之,它非常依赖于编译器供应商。
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);
            }
        }
    }
}


推荐