【问题标题】:Static methods with same signature after type erasure in JavaJava中类型擦除后具有相同签名的静态方法
【发布时间】:2019-10-15 11:41:47
【问题描述】:

这是关于在 Java 中使用参数化类型的签名方法的另一个问题。

假设你有以下两种方法:

    static void f(List<Integer> l) {}
    static void f(List<String> l) {}

编译器会抱怨两种方法在类型擦除后具有相同的签名(两种参数类型都被擦除为 List)。

stackoverflow 上的许多类似问题都问为什么会这样,但问题总是与实例(非静态)方法有关(例如,参见 Method has the same erasure as another method in type)。

通常一半的答案基于以下(非常错误的)论点:编译器会删除字节码中的所有类型参数并使方法无法区分。好的,只需使用javap 打印字节码,您就会看到是否所有内容都被删除了! (虽然字节码丢失了很多参数化数据,但实际上保留了完整的方法签名,当您想使用包含泛型类和方法的依赖项编译新类时,这绝对有用)。

另一方面,最佳答案通常引用 JLS 8.4.2 并解释说,为了与旧的、预泛型的 Java 版本(以及较新版本中的原始类型)兼容,禁止使用具有覆盖等效签名的方法。

我对后一个参数没意见,除了它只意味着实例方法(非静态),因为静态方法无论如何都不能被覆盖。

对于静态方法可能有类似的解释,但我无法确定它。有人可以帮我理解这一点吗?

【问题讨论】:

  • 关于编译器执行类型擦除的答案没有任何“非常错误”的地方。这只是一个有据可查的事实,static 与实例方法无关;正确地说,方法签名信息也根本不是字节码的一部分。
  • @user207421 我想你误读了我。我不怀疑类型擦除。例如,您无法在运行时获得关于类型参数的任何信息,这是公然的。我只是说很多人在说字节码不包含泛型时误解了类型擦除。这是错误的。顺便说一句,您是否尝试过在泛型类上运行命令javap?也许你会感到惊讶......
  • 啊,顺便说一下,来自JVM规范:docs.oracle.com/javase/specs/jvms/se13/html/…

标签: java generics types jvm javac


【解决方案1】:

向后兼容的论点仍然成立。

如果您有这段代码(不使用泛型,强烈反对,但即使在今天也是合法的,并且在应该仍然编译的 Java 1.4 代码中完全正常),编译器应该选择您的两种方法中的哪一种?

List rawList = new ArrayList();
YourClass.f(rawList);

更重要的是,假设您以某种方式选择两者之一,在生成的调用站点字节码中,泛型仍然被删除,因此在运行时,JVM 不知道您的意思是两个f(List) 中的哪一个。方法调用指定方法名称和签名,但该签名不包括泛型。这不是由于兼容性问题。他们是否可以尝试通过诸如具有扩展调用规范的新操作码之类的东西来更加努力地推动这一点?也许。但现在就是这样。

另一方面,最佳答案通常引用 JLS 8.4.2 并解释说,为了与旧的、预泛型的 Java 版本(以及较新版本中的原始类型)兼容,禁止使用具有覆盖等效签名的方法。

我对后一个参数没意见,除了它只意味着实例方法(非静态),因为静态方法无论如何都不能被覆盖。

好吧,你不能覆盖静态方法,但你的两个方法仍然是“覆盖等效的”,这意味着它们有一个非常接近的签名,你一次只能有一个(在子类中)在这种情况下,如果因此被继承,一个会覆盖另一个——但这也意味着您不能在同一个类上拥有两个这样的方法。

请注意,这不会造成任何实际问题,因为您始终可以通过更改为不同的方法名称来避免“重载”。

【讨论】:

  • 我知道模棱两可是多么尴尬。但这与重载几乎是相同的问题。假设您有以下方法:static void f(Number x, Object y) {}static void f(Object x, String y) {} 在同一类 C。此类将编译罚款,尽管调用 C.f(new Object(), new Object()) 会模棱两可。出于某种原因,java 背后的团队决定只有 this 不会编译,而不是潜在冲突重载的定义。我只是想知道为什么在泛型方法方面选择了不同的方法。
  • 不同的是,重载错误会在编译时解决。在生成字节码中,它会说 with 重载来选择。虽然为调用 f 生成的字节码不包含泛型类型,但它只会链接到采用 Listf
  • 我的例子只是关于静态方法。如果编译器接受具有与参数化相似的签名的两种方法,那么在字节码中它们将只是两种不同的方法,具有不同的地址。编译器可以毫无问题地处理这个问题。顺便说一句,字节码中的调用站点(invokestatic 指令)只包含一个方法索引,而不是签名,所以这里真的没有什么特别的(我的意思是,就好像这两个方法有不同的名字一样)。
  • 该方法索引将由签名解析。它不能被编译。你将如何处理类演化(如果你编译旧版本的 jar,你的代码仍然会在新版本上运行,即使很多方法已经重新安排,只要因为与您的签名匹配的那个仍然存在)。
  • 在您链接到的 JVM 规范中,您将看到调用站点方法描述符(用于动态解析被调用的方法)仅存储参数的类名,而不存储任何泛型:docs.oracle.com/javase/specs/jvms/se13/html/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-19
  • 1970-01-01
  • 2011-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-16
  • 1970-01-01
相关资源
最近更新 更多