如果您查看源代码1,您会发现只有一个名称为getSimpleName()
. 此方法返回com.sun.tools.javac.util.Name
. 有两个关键的事情需要注意:
- 该方法实际上是重写
com.sun.source.tree.ClassTree#getSimpleName()
的,它被声明为 return javax.lang.model.element.Name
。
com.sun.tools.javac.util.Name
抽象类实现了接口,并且由于被覆盖的javax.lang.model.element.Name
方法返回前者,它利用了协变返回类型。
根据这个 Oracle 博客,一个覆盖另一个但声明协变返回类型的方法是使用桥接方法实现的。
这是如何实施的?
尽管 java 语言不允许基于返回类型的重载,但 JVM 始终允许基于返回类型的重载。JVM 使用方法的完整签名进行查找/解析。完整签名除了参数类型之外还包括返回类型。即,一个类可以有两个或多个仅在返回类型上有所不同的方法。javac 使用这个事实来实现协变返回类型。在上面的 CircleFactory 示例中,javac 生成的代码等价于以下内容:
class CircleFactory extends ShapeFactory {
public Circle newShape() {
// your code from the source file
return new Circle();
}
// javac generated method in the .class file
public Shape newShape() {
// call the other newShape method here -- invokevirtual newShape:()LCircle;
}
}
我们可以在类上使用带有 -c 选项的 javap 来验证这一点。请注意,我们仍然不能在源语言中使用基于返回类型的重载。但是,javac 使用它来支持协变返回类型。这样,在 JVM 中无需更改即可支持协变返回类型。
事实上,如果你运行以下命令:
javap -v com.sun.tools.javac.tree.JCTree$JCClassDecl
将输出以下内容(仅包括相关方法):
public com.sun.tools.javac.util.Name getSimpleName();
descriptor: ()Lcom/sun/tools/javac/util/Name;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #13 // Field name:Lcom/sun/tools/javac/util/Name;
4: areturn
LineNumberTable:
line 801: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/sun/tools/javac/tree/JCTree$JCClassDecl;
和:
public javax.lang.model.element.Name getSimpleName();
descriptor: ()Ljavax/lang/model/element/Name;
flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #96 // Method getSimpleName:()Lcom/sun/tools/javac/util/Name;
4: areturn
LineNumberTable:
line 752: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/sun/tools/javac/tree/JCTree$JCClassDecl;
如您所见,第二种方法,即返回的方法,javax.lang.model.element.Name
既是综合的,也是桥梁的。换句话说,该方法由编译器生成,作为协变返回类型实现的一部分。它还简单地委托给“真实”方法,即源代码中实际存在的返回com.sun.tools.javac.util.Name
.
1. 源代码链接适用于 JDK 13。