我在实现一个大量使用泛型的Java接口时发现了一个奇怪的不一致,我无法找到解释为什么会发生这种情况的原因。
我尽可能地删除了这个例子,我知道在这个例子中,泛型的使用不再有意义。
public interface Service<T> {
public List<T> thisCompiles();
public List<T> andThisCompiles(List<Object> inputParam);
public <S extends T> List<S> thisCompilesAswell();
public <S extends T> List<S> evenThisCompiles(List inputParam);
public <S extends T> List<S> butThisDoesnt(List<Object> inputParam);
}
public class ServiceImpl implements Service<Number> {
@Override
public List<Number> thisCompiles() {
return null;
}
@Override
public List<Number> andThisCompiles(List<Object> inputParam) {
return null;
}
@Override
public List<Number> thisCompilesAswell() {
return null;
}
@Override
public List<Number> evenThisCompiles(List inputParam) {
return null;
}
@Override
public List<Number> butThisDoesnt(List<Object> inputParam) {
return null;
}
}
请注意,在实现中,所有返回类型都是List<Number>
,尽管界面更加慷慨。
编译此代码段时(我尝试了Oracle JDK 8 u144和OpenJDK 8 u121),我收到以下错误消息:
butThisDoesnt(List<Object>)
的方法ServiceImpl
必须覆盖或实现超类型方法ServiceImpl
必须实现继承的抽象方法Service<Number>.butThisDoesnt(List<Object>)
butThisDoesnt(List<Object>)
类型的方法ServiceImpl
与类型butThisDoesnt(List<Object>)
的{{1}}具有相同的删除权,但不会覆盖它一旦我传递给函数的参数本身有一些泛型参数,似乎编译失败。
将第五个功能实现为
Service<T>
按预期工作。
是否有这种行为的解释,或者我偶然发现了一个错误(虽然它也发生在OpenJDK中)?为什么第5个函数表现不同?
我不知道如何编译这段代码(我为我的代码库修复了它)。我只是偶然发现了这个问题并对逻辑解释感到好奇为什么编译器接受前四种方法但拒绝第五种方法。
答案 0 :(得分:2)
我已经包含了该问题的简化版本。在以下示例中,由于方法broken
,程序将无法编译。
import java.util.List;
import java.util.ArrayList;
public class Main{
static interface Testing{
<S> List<S> getAList();
<S> List<S> broken(List<String> check);
}
static class Junk implements Testing{
public List<Number> getAList(){
return new ArrayList<>();
}
public List<Number> broken(List<String>check){
return new ArrayList<>();
}
}
public static void main (String[] args) throws java.lang.Exception
{
// your code goes here
}
}
有两个错误和一个警告。
Main.java:11:错误:垃圾不是抽象的,不会覆盖
测试静态类垃圾中的抽象方法(List)
实现测试{
^其中S是一个类型变量:
S扩展了方法broken(List)
Main.java:12:警告:Junk实现中的[unchecked] getAList() 测试公共列表中的getAList()getAList(){ ^返回类型需要从列表到列表的未经检查的转换,其中S是类型变量: S扩展了方法getAList()
Main.java:15:错误:名称冲突:破坏(List)在垃圾和 测试中的破坏(List)具有相同的擦除,但两者都没有 覆盖其他公共列表已损坏(Listcheck){ ^其中S是一个类型变量: S扩展了方法broken(List)
2个错误 1警告
为什么第一种方法推断出一个演员而且只发出警告?
“定义中允许未经检查的转换,尽管不健全,作为允许从非通用代码顺利迁移到通用代码的特殊限制。”
https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.5
另一种方法在参数列表中有一个Generic,因此它不是从以前的非泛型代码迁移,而且它不健全,应该被视为错误。
答案 1 :(得分:1)
您的第五种方法的问题是,可能会认为以下代码段有效:
List<Integer> ints = new ArrayList<>(); // this works as expected
List<Number> number = new ArrayList<>(); // this works too
number = ints; // this doesn't work
也就是说,因为泛型不存在层次关系,所以List<Number>
与List<Integer>
无关。
这是由于类型擦除,泛型仅在编译期间使用。在运行时,您只有List
- 对象(类型擦除=在运行时不再可用的类型)。
在界面中,该方法定义为<S extends T, V> List<S> butThisDoesnt(List<V> inputParam);
,其中S
可以是Number
的任何子类型。如果我们现在将上述内容应用于您的实现,那么它应该是有意义的。
public List<Number> butThisDoesnt(List<Object> inputParam) {
return null;
}
List<Number>
与界面中的定义发生冲突,因为List<Number>
可以包含任何Number
,但不能是List<Integer>
。
了解更多类型删除 in this other question。在this page上。
注意如果我错了或者我错过了什么,请纠正我。