我有一个界面:
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>
以便编译)
因此,擦除,最简单的解释,不是原因,或者至少需要详细说明 - 类型存在,而且,您不关心类型解析,因为您已经有两种方法具有独特的签名。或者至少看起来如此。
答案 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的此解决方案