考虑以下表达式集:
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
中的覆盖?
最佳答案
形式的调用 super.method()
允许绕过覆盖 method()
在同一个类中,调用最具体的 method()
父类(super class)层次结构。因为,在字节码级别,只有声明类本身可以忽略它自己的重写方法(以及子类的潜在重写方法),如果这种调用应该由不同的(但概念上称为) 类,就像它的内部类之一,使用 Outer.super.method(...)
的形式,或为方法引用生成的合成类。
请注意,即使一个类没有覆盖被调用的方法并且看起来没有什么区别,调用也必须以这种方式编译,因为在运行时可能有子类覆盖该方法,然后,它会有所不同.
有趣的是,使用 T.super.method()
时也会发生同样的事情。什么时候T
实际上不是外部类,而是包含语句的类。在那种情况下,辅助方法并不是真正必要的,但编译器似乎实现了所有形式的调用 identifier.super.method(...)
统一。
作为旁注,Oracle 的 JRE 能够在为 lambda 表达式/方法引用生成类时绕过此字节码限制,因此,super::methodName
的方法引用不需要访问器方法。 kind,可以表示为:
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
将返回类似的东西 LambdaSuper@6b884d57
表明它调用了重写的 Object.toString()
方法而不是覆盖 LambdaSuper.toString()
.似乎编译器供应商对 JRE 功能的期望很谨慎,不幸的是,这部分似乎有点不明确。
不过,对于真正的内部类场景,它们是是必需的。
关于java - 为什么 'T.super.toString()' 和 'super::toString' 使用合成访问器方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34675012/