[英]Why is it impossible to implement a generic interface multiple times with different type parameters?
I have an interface: 我有一个界面:
public static interface Consumer<T> {
void consume(T t);
}
And I want to be able to have: 我希望能够拥有:
public static class Foo implements Consumer<String>, Consumer<Integer> {
public void consume(String t) {..}
public void consume(Integer i) {..}
}
It doesn't work - the compiler doesn't let you implement the same interface twice. 它不起作用 - 编译器不允许您两次实现相同的接口。
The question is: Why? 问题是: 为什么?
People have asked similar questions here, but the answer was always "type erasure", ie you cannot do it because the types are erased at runtime. 人们在这里问了类似的问题,但答案总是“类型擦除”,即你不能这样做,因为类型在运行时被删除。
And they aren't - some types are retained at runtime. 它们不是 - 某些类型在运行时保留。 And they are retained in this particular case:
它们保留在这种特殊情况下:
public static void main(String[] args) throws Exception {
ParameterizedType type = (ParameterizedType) Foo.class.getGenericInterfaces()[0];
System.out.println(type.getActualTypeArguments()[0]);
}
This prints class java.lang.String
(if I only keep Consumer<String>
in order to compile) 这打印
class java.lang.String
(如果我只保留Consumer<String>
以便编译)
So, erasure, in its simplest explanation, is not the reason, or at least it needs elaboration - the type is there, and also, you don't care about the type resolution, because you already have two methods with distinct signature. 因此,擦除,在其最简单的解释中,不是原因,或者至少它需要详细说明 - 类型存在,而且,您不关心类型解析,因为您已经有两个具有不同签名的方法。 Or at least it seems so.
或者至少看起来如此。
The answer is still "type erasure", but it's not that simple. 答案仍然是“类型擦除”,但并不是那么简单。 The keywords are: raw types
关键字是: 原始类型
Imagine the following: 想象一下:
Consumer c = new Foo();
c.consume(1);
What would that do? 那会怎么样? It appears that
consume(String s)
is not actually consume(String s)
- it is still consume(Object o)
, even though it is defined to take String
. 似乎
consume(String s)
实际上并不consume(String s)
- 它仍然被consume(Object o)
,即使它被定义为使用String
。
So, the above code is ambiguous - the runtime can't know which of the two consume(..)
methods to invoke. 因此,上面的代码是不明确的 - 运行时无法知道要调用的两个
consume(..)
方法中的哪一个。
A funny follow-up example is to remove Consumer<Integer>
, but keep the consume(Integer i)
method. 一个有趣的后续示例是删除
Consumer<Integer>
,但保留consume(Integer i)
方法。 Then invoking the c.consume(1)
on a raw Consumer
. 然后在原始
Consumer
上调用c.consume(1)
。 throws ClassCastException
- unable to cast from Integer to String. 抛出
ClassCastException
- 无法从Integer转换为String。 The curious thing about this exception is that it happens on line 1 . 关于这个例外的奇怪之处在于它发生在第1行 。
The reason is the use of bridge methods . 原因是使用桥接方法 。 The compiler generates the bridge method:
编译器生成桥接方法:
public void consume(Object o) {
consume((String) o);
}
The generated bytecode is: 生成的字节码是:
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
So even if the methods you have defined keep their signatures, each of them has a corresponding bridge method that is actually invoked when working with the class (regardless whether it's raw or parameterized). 因此,即使您定义的方法保留了它们的签名,它们中的每一个都有一个相应的桥接方法,在使用该类时实际调用它(无论它是原始的还是参数化的)。
@Bozho: Thanks for your profound question and your own answer. @Bozho:感谢您提出深刻的问题和您自己的答案。 Even I was at this doubt at some stage.
即使我在某个阶段遇到这种疑问。
Plus to add more to your answer, I'd disagree with you on this point in your question: 另外,为了在答案中添加更多内容,我在您的问题中不同意这一点:
"some types are retained at runtime" . “某些类型在运行时保留” 。
Answer is NO because of type erasure (which you already pointed it out). 由于类型擦除(你已经指出了),答案是否定的 。 Nothing is retained.
什么都没有保留。
Now to answer this key doubt, " how does it then knows the exact type after erasure ? ", check out the very definition of ParameterizedType . 现在回答这个关键疑问,“ 它如何知道擦除后的确切类型? ”,查看ParameterizedType的定义。
When a parameterized type p is created, the generic type declaration that p instantiates is resolved
创建参数化类型p时,将解析p实例化的泛型类型声明
May be this resolution of exact TypeVariable is obtained through what you specified as bridge methods 可能是这个精确TypeVariable的分辨率是通过你指定的桥接方法获得的
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.