【问题标题】:Why do generic types have the same signature?为什么泛型类型具有相同的签名?
【发布时间】:2013-07-03 21:19:04
【问题描述】:

我有以下通用类:

class Or<A,B>
{
  Or (A a) {}
  Or (B b) {}
}

为什么我尝试编译时会出现以下错误:

Or(A) 已在 Or 中定义 或 (B b) ^

在我看来,这两个构造函数共享相同的签名,尽管它们具有不同的泛型类型参数。为什么?以及如何解决这个问题?

更新

我现在明白了这个问题。编译器需要一种方法来区分这两种类型。对于我的用例来说,添加这样的约束就可以了。所以我想补充一个问题:

如何指定 A 和 B 两种类型可以完全不同?

【问题讨论】:

  • 旧版type erasure
  • @johnchen902,您可以将此评论添加为答案吗?会很棒
  • @PrasadKharkar 因为这个问题显然是重复的。在看到有人 10k 或更多答案之前,我不想成为第一个回答者。
  • 你不能有类型的联合,见:stackoverflow.com/questions/6592832/…
  • 请记住,Java 泛型是编译器虚构的。编译后的代码中基本上没有任何内容可以“记住”它们是通用的。

标签: java generics


【解决方案1】:

在我看来,这两个构造函数共享相同的签名,尽管它们具有不同的泛型类型参数。

确实如此。签名是

Or(Object o);

为什么?

由于type erasure 在Java 中实现泛型:对泛型类型的引用在所有使用它们的上下文中都转换为System.Object;泛型类型只有编译器知道。

以及如何解决这个问题?

不幸的是,您无法在构造函数中轻松解决此问题。你可以用工厂方法替换重载的构造函数,并给出不同的名字,比如OrWithAOrWithB

// Hide the constructor
private Or(...) {
    ...
}
// Publish factory methods
public static <X> Or OrWithA(X a) {
    return new Or(...);
}
public static <X> Or OrWithB(X a) {
    return new Or(...);
}

【讨论】:

  • 不起作用,因为non-static class A cannot be referenced from a static context
  • @ceving 谢谢,在使用 C# 几年后,我忘记了 Java 泛型的一些限制。您可以使您的工厂方法在其他类型上通用(参见编辑),尽管它肯定不如使用 AB 那样优雅。
【解决方案2】:

这是因为 A 或 B 可以是任何东西,它们也可以相同,因为泛型只是用于编译时间。在运行时,它们会因为类型擦除而丢失

【讨论】:

  • 与运行时类型无关。
【解决方案3】:

他们就是这样做的。这就是泛型的本质。它们提供仅在编译时使用的语法糖。没有办法绕过它。

(确认问题cmets)这称为类型擦除:见http://en.wikipedia.org/wiki/Type_erasure

【讨论】:

    【解决方案4】:

    这是由于type erasure。 Eclipse 编译器给出了更详细的错误: Method Or(A) has the same erasure Or(Object) as another method in type Or

    如果你对泛型应用限制,它编译得很好:

    类或
    {
        或(A a){}
    
        或(B b) {}
    }

    【讨论】:

    • 我需要约束:A 扩展除了 B 的类之外的任何东西。类型应该是不同的。如何指定?
    • @ceving 不能指定像“除 B 之外的任何东西”这样通用的东西。您需要两个不同的具体类或接口。
    【解决方案5】:

    换种说法:你有两种类型,A 和 B,但对它们一无所知。所以一种完全未知的类型和另一种一样好。构造函数调用应该如何调度?

    【讨论】:

      【解决方案6】:

      这是不合法的,因为泛型在运行时被丢弃(这是类型擦除)。你的两个方法都有Or(Object)的原型。

      唯一的解决方案是使用OrA()OrB() 方法——或者完全复习您的课程。

      【讨论】:

        【解决方案7】:

        这是因为 type erasure: 泛型在编译时被替换为 Object 类型,因此您的方法确实具有相同的签名。作为一种解决方法,您可以选择缩小 A 和 B 的类型:

        public class Test<A extends String, B extends Number> {
        
        public Test(A arg){
        
        }
        
        public Test(B arg){
        
        }
        }
        

        【讨论】:

          【解决方案8】:

          这些构造函数将被视为具有相同的签名,因为它们的类型在运行时无法识别。

          如果可能,请尝试在至少一个类型参数上指定边界。

          class Or<A extends Number,B>
          {
            Or (A a) {}
            Or (B b) {}
          }
          

          【讨论】:

            【解决方案9】:
            1. 考虑一下,调用哪个构造函数?

              Or<Integer,Integer> o = new Or<>(5);
              
            2. 您的问题确实来自类型擦除,它使您的代码在编译后看起来像这样:

              class Or
              {
                  Or (Object a) {}
                  Or (Object b) {}
              }
              

            【讨论】:

            • 如何指定类型可以是不同的?这对我的用例来说很好,对编译器来说应该足够了。
            • @ceving 不,你不能,恐怕。但是也许你可以使用factory method
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多