为什么 '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);
}
}
}
}
综上所述,它非常依赖于编译器供应商。
考虑以下一组表达式:
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);
}
}
}
}
综上所述,它非常依赖于编译器供应商。