我偶然发现了java编译器行为的违反直觉的例子(在this ru.so问题中)。鉴于javac 1.8.0_161及以下代码:
public class RuSo791351 {
public static void main(String[] args) {
List<Integer> payload = Arrays.asList(1, 2, 3);
Consumer uncheckedConsumer = new Consumer();
uncheckedConsumer.consume(payload);
Consumer<?> wildcardConsumer = new Consumer<>();
wildcardConsumer.consume(payload);
Consumer<Integer> typedConsumer = new Consumer<>();
typedConsumer.consume(payload);
}
private static class Consumer<T> {
public <V> void consume(Collection<V> collection) {
System.out.println("consume(Collection<T>) called");
}
public void consume(List<String> strings) {
System.out.println("consume(List<String>) called");
}
}
}
编译器应该考虑将类型擦除考虑在内并采取简单的方法,每次都为consume(List<String>)
调用发送字节码,或者采取艰难的方式并为consume(Collection<V>)
发出字节码 - 再次,每一个时间。但是,编译器为uncheckedConsumer发出consume(List<String>)
,为其他情况发出consume(Collection<V>)
:
40: invokevirtual #7 // Method me/etki/wtf/so/RuSo791351$Consumer.consume:(Ljava/util/List;)V
...
54: invokevirtual #8 // Method me/etki/wtf/so/RuSo791351$Consumer.consume:(Ljava/util/Collection;)V
...
70: invokevirtual #8 // Method me/etki/wtf/so/RuSo791351$Consumer.consume:(Ljava/util/Collection;)V
所以看来如果我根本没有为Consumer类指定<T>
参数,编译器会忽略类中包含的所有通用信息 - 即使<V>
是一个完全不同的方法参数 - 和编译器选择List over Collection作为更精确的类型。我几乎可以肯定这是对前通用时代代码的向后兼容性的影响,但在JLS中找不到任何特定的东西(说实话,我似乎甚至不知道如何向谷歌询问这个问题)。有人能指出我这种行为的规范吗?