为什么不可能使用不同的类型参数多次实现通用接口?

时间:2014-04-02 09:18:03

标签: java generics

我有一个界面:

public static interface Consumer<T> {
    void consume(T t);
}

我希望能够拥有:

public static class Foo implements Consumer<String>, Consumer<Integer> {
    public void consume(String t) {..}
    public void consume(Integer i) {..}
}

它不起作用 - 编译器不允许你两次实现相同的接口。

问题是:为什么?

人们在这里提出了类似的问题,但答案总是&#34;键入擦除&#34;,即你不能这样做,因为类型在运行时被删除。

并且它们不是 - 某些类型在运行时保留。并且在这种特殊情况下保留它们:

public static void main(String[] args) throws Exception {
    ParameterizedType type = (ParameterizedType) Foo.class.getGenericInterfaces()[0];
    System.out.println(type.getActualTypeArguments()[0]);
}

这会打印class java.lang.String(如果我只保留Consumer<String>以便编译)

因此,擦除,最简单的解释,不是原因,或者至少需要详细说明 - 类型存在,而且,您不关心类型解析,因为您已经有两种方法具有独特的签名。或者至少看起来如此。

2 个答案:

答案 0 :(得分:27)

答案仍然是&#34;键入erasure&#34;,但事情并非如此简单。关键字为:原始类型

想象一下:

Consumer c = new Foo();
c.consume(1);

那会怎么做?看来consume(String s)实际上不是consume(String s) - 它仍然是consume(Object o),即使它定义为String

因此,上面的代码含糊不清 - 运行时无法知道要调用的两个consume(..)方法中的哪一个。

一个有趣的后续示例是删除Consumer<Integer>,但保留consume(Integer i)方法。然后在原始c.consume(1)上调用Consumer。抛出ClassCastException - 无法从Integer转换为String。关于此异常的奇怪之处在于它发生在第1行

原因是使用bridge methods。编译器生成桥接方法:

public void consume(Object o) {
    consume((String) o);
}

生成的字节码是:

public void consume(java.lang.String);

public void consume(java.lang.Integer);

public void consume(java.lang.Object);
  Code:
     0: aload_0
     1: aload_1
     2: checkcast     #39                 // class java/lang/String
     5: invokevirtual #41                 // Method consume:(Ljava/lang/String;)V

因此,即使您定义的方法保留了它们的签名,每个方法都有一个相应的桥接方法,在使用该类时实际调用它(无论它是原始的还是参数化的)。

答案 1 :(得分:3)

@Bozho:感谢您提出深刻的问题和自己的答案。即使我在某个阶段就遇到了这种疑问。

另外,为了在答案中添加更多内容,我在您的问题中不同意您的观点:

&#34;某些类型在运行时保留&#34;

答案是,因为类型擦除(您已经指出了它)。什么都没有保留。

现在回答这个关键疑问,&#34; 如何知道删除后的确切类型?&#34;,查看ParameterizedType的定义。< / p>

  

创建参数化类型p时,解析p实例化的泛型类型声明

可以通过您指定为TypeVariable

的内容获得确切bridge methods的此解决方案