简体   繁体   English

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

[英]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.

相关问题 为什么通用类可以实现具有不同类型参数的接口? - Why is it possible for a generic class to implement interface with different type parameters? 在Java中使用通用类型实现通用接口 - Implement generic interface with generic type in Java 通用方法中的绑定类型参数在等效通用接口工作时失败,为什么? - Bound Type Parameters in a Generic Method fail while an equivalent Generic Interface works, why? 抽象类实现了通用的接口类型 - Abstract class implement a generic Interface Type 使用泛型参数扩展接口的方法签名实现接口 - Implement an interface with a method signature whose generic parameters extend an interface 实现没有类型参数的通用接口 - Implement a generic interface without type parameter 如何在Clojure中使用具体类型参数实现接口? - How to implement an interface with concrete type parameters in Clojure? 在继承层次结构中使用不同的泛型类型实现两次通用接口 - Implement generic interface twice in inheritance hierarchy with different generic types 为什么许多Java Stream接口方法在参数中使用较低的有界通配符而不是泛型类型? - Why many Java Stream interface methods use lower bounded wildcard in parameters instead of generic type? 用于枚举类型的约束泛型类型来实现某些接口 - Constraint generic type for enum type to implement some interface
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM