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

Why 'T.super.toString()' and 'super::toString' use a synthetic accessor method?

考虑以下一组表达式:

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[=13=](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[=13=](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[=13=]:(LT;)Ljava/lang/String;
    1 : # 40 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:...
        #46 invokestatic T.access[=13=]:(LT;)Ljava/lang/String;
}

为什么java代码行/*2*//*3*//*4*/生产和使用合成访问器方法 access[=19=]?我希望行 /*2*/ 和 bootstrap 方法用于行 /*3*//*4*/ 也像行 /*1*/ 那样使用 invokespecial

特别是当方法 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 中的覆盖?

super.method() 形式的调用允许绕过同一个 class 中的重写 method(),调用超级 class 中最具体的 method() ] 等级制度。因为,在字节码级别,只有声明 class 本身可以忽略它自己的重写方法(以及 subclasses 的潜在重写方法),如果这种调用将生成合成访问器方法应该由不同的(但概念上有资格的)class 执行,例如其内部 classes 之一,使用 Outer.super.method(...) 形式,或为方法生成的合成 class参考。

请注意,即使 class 没有覆盖调用的方法并且看起来没有区别,调用也必须以这种方式编译,因为可能有 subclasses运行时覆盖方法,然后,它会有所作为。

有趣的是,当 T 实际上不是外部 class 而是包含语句的 class 时,使用 T.super.method() 时会发生同样的事情。在那种情况下,辅助方法并不是真正必要的,但编译器似乎统一实现了 identifier.super.method(...) 形式的所有调用。


作为旁注,Oracle 的 JRE 在为 lambda expressions/method 引用生成 classes 时能够绕过此字节码限制,因此,访问器方法不需要访问方法引用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";
    }
}

生成的 Supplier 将 return 类似 LambdaSuper@6b884d57 表明它调用了覆盖的 Object.toString() 方法而不是覆盖的 LambdaSuper.toString() 方法。似乎编译器供应商对 JRE 功能的期望很谨慎,不幸的是,这部分似乎有点不明确。

不过,对于真正的内部 class 场景,它们 所必需的。

已经解释了 为什么 它正在发生 — super 引用仅限于直系子 class。这只是那里真正发生的事情的更详细的版本:


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

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

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

class T {
    static synthetic String access[=11=](T t) { // executing accessor
        return t.super.toString(); // only for the refered outer class
    }
    class U { // new U(T.this)
        static synthetic T access[=11=](U u) { // relaying accessor
            return T.this; // for every intermediate outer class
        }
        class V {{ // new V(U.this)
            T.access[=11=](U.access[=11=](U.this)); // T.access[=11=](T.this)
        }}
    }
}

T是直接封闭的class时,即没有中间外部classes,只有"executing"访问器在class中生成T(即本身,这似乎是不必要的)。

N.B.: 访问器链由 Eclipse 生成,而不是由 OpenJDK 生成,见下文。


方法引用自己的超class'方法

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

这会生成一个合成访问器方法和一个委托给它的 bootstrap 方法:

class T {
    class U {
        class V {
            static synthetic String access[=13=](V v) {
                return v.super.toString();
            }
            dynamic bootstrap Supplier get(V v) { // methodref
                return () -> V.access[=13=](v); // toString() adapted to get()
            }
            {
                get(V.this);
            }
        }
    }
}

这是一个类似于前一个的奇异情况,因为 super::toString 在这里等同于 V.super::toString,所以合成访问器是在 class V 本身中生成的.这里的一个新元素是 bootstrap 方法,用于将 Object::toString 适配为 Supplier::get

N.B.: 这里只有 OracleJDK "smart" 足够(正如 指出的那样)通过放置super 直接调用方法引用适配器。


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

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

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

class T {
    static synthetic String access[=15=](T t) { // executing accessor
        return t.super.toString(); // only for the refered outer class
    }
    class U { // new U(T.this)
        static synthetic T access[=15=](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[=15=](t); // toString() adapted to get()
            }
            {
                get(U.access[=15=](U.this)); // get(T.this)
            }
        }
    }
}

这里没什么新鲜的,只需要注意内部 class 总是只接收直接外部 class 的实例,所以在 class V 中,使用 T.this 它可能会经历整个中间合成访问器方法链,例如U.access[=28=](V.U_this)(如 Eclipse),或利用这些合成字段(引用 outer.this)的包可见性并将 T.this 转换为 V.U_this.T_this(如 OpenJDK)。


N.B.: 以上翻译是根据 Eclipse 编译器。 OpenJDK 的不同之处在于为方法引用生成 instance 合成 lambda 方法,而不是 static 合成访问器方法 Eclipse 会,并且还避免访问器链,所以在最后一种情况下 OpenJDK 发出:

class T {
    static synthetic String access[=16=](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[=16=]() {
                return T.access[=16=](V.U_this.T_this); // direct relay
            }
            dynamic bootstrap Supplier get(V v) { // methodref
                return () -> V.lambda[=16=](v); // lambda[=16=]() adapted to get()
            }
            {
                get(V.this);
            }
        }
    }
}


综上所述,它非常依赖于编译器供应商。