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

2022-09-01 12:09:01

我有一个接口:

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) {..}
}

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

问题是:为什么?

人们在这里问过类似的问题,但答案总是“类型擦除”,即你不能这样做,因为类型在运行时被擦除。

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

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

这打印(如果我只是为了编译而保留)class java.lang.StringConsumer<String>

因此,在最简单的解释中,擦除不是原因,或者至少它需要详细说明 - 类型就在那里,而且,您不关心类型解析,因为您已经有两个具有不同签名的方法。或者至少看起来是这样。


答案 1

答案仍然是“类型擦除”,但并不是那么简单。关键字是:原始类型

想象一下:

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

那会有什么用呢?看起来实际上不是 - 它仍然是,即使它被定义为采取。consume(String s)consume(String s)consume(Object o)String

因此,上面的代码是模棱两可的 - 运行时无法知道要调用两种方法中的哪一个。consume(..)

一个有趣的后续例子是删除,但保留该方法。然后在原始 .throws - 无法从整数转换为字符串。关于此异常的奇怪之处在于它发生在第 1 行Consumer<Integer>consume(Integer i)c.consume(1)ConsumerClassCastException

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

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

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


答案 2

@Bozho:谢谢你的深刻问题和你自己的答案。甚至我在某个阶段也处于这种怀疑之中。

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

“某些类型在运行时保留”。

答案是否定的,因为类型擦除(您已经指出了这一点)。不保留任何内容。

现在要回答这个关键问题,“它如何知道擦除后的确切类型?”,请查看ParabellizedType的定义。

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

可能是这种精确类型的分辨率可变是通过您指定的桥接方法获得的


推荐