我有一个界面:
public static interface Consumer<T> {
void consume(T t);
}
Run Code Online (Sandbox Code Playgroud)
我希望能够拥有:
public static class Foo implements Consumer<String>, Consumer<Integer> {
public void consume(String t) {..}
public void consume(Integer i) {..}
}
Run Code Online (Sandbox Code Playgroud)
它不起作用 - 编译器不允许您两次实现相同的接口.
问题是:为什么?
人们在这里问了类似的问题,但答案总是"类型擦除",即你不能这样做,因为类型在运行时被删除.
它们不是 - 某些类型在运行时保留.它们保留在这种特殊情况下:
public static void main(String[] args) throws Exception {
ParameterizedType type = (ParameterizedType) Foo.class.getGenericInterfaces()[0];
System.out.println(type.getActualTypeArguments()[0]);
}
Run Code Online (Sandbox Code Playgroud)
这打印class java.lang.String(如果我只保留Consumer<String>以便编译)
因此,擦除,在其最简单的解释中,不是原因,或者至少它需要详细说明 - 类型存在,而且,您不关心类型解析,因为您已经有两个具有不同签名的方法.或者至少看起来如此.
Boz*_*zho 27
答案仍然是"类型擦除",但并不是那么简单.关键字是:原始类型
想象一下:
Consumer c = new Foo();
c.consume(1);
Run Code Online (Sandbox Code Playgroud)
那会怎么样?它似乎consume(String s)实际上并不是consume(String s)- 它仍然是consume(Object o),即使它被定义为采取String.
因此,上面的代码含糊不清 - 运行时无法知道consume(..)要调用的两个方法中的哪一个.
一个有趣的后续示例是删除Consumer<Integer>,但保留consume(Integer i)方法.然后调用c.consume(1)原始的Consumer.throws ClassCastException- 无法从Integer转换为String.关于这个例外的奇怪之处在于它发生在第1行.
原因是使用桥接方法.编译器生成桥接方法:
public void consume(Object o) {
consume((String) o);
}
Run Code Online (Sandbox Code Playgroud)
生成的字节码是:
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
Run Code Online (Sandbox Code Playgroud)
因此,即使您定义的方法保留了它们的签名,每个方法都有一个相应的桥接方法,在使用该类时实际调用它(无论它是原始的还是参数化的).