为什么这个类编译即使它没有正确实现其接口?

时间:2013-07-18 08:18:14

标签: java generics

一个List然后为什么代码(下面)编译?当然MyClass2应该返回List<Integer>

public class Main {
  public static void main(final String[] args) {
    MyClass myClass = new MyClass();
    List list = myClass.getList();
    System.out.println(list);
    System.out.println(list.get(0).getClass());

    MyClass2 myClass2 = new MyClass2();
    List list2 = myClass2.getList();
    System.out.println(list2);
    System.out.println(list2.get(0).getClass());
  }

  public interface Int1 {
    public List getList();
  }

  public interface Int2 extends Int1 {
    @Override
    public List<Integer> getList();
  }

  public static class MyClass implements Int2 {
    @Override
    public List<Integer> getList() {
      return Arrays.asList(1, 2, 3);
    }
  }

  public static class MyClass2 implements Int2 {
    @Override
    public List getList() {
      return Arrays.asList("One", "Two", "Three");
    }
  }
}

我注意到如果你试图使它成为List<String>,那么你会得到一个错误“java:Main.MyClass2不是抽象的,并且不会覆盖Main.Int2中的抽象方法getList()”。我不太明白为什么你在上面的例子中没有得到这个。

注意:我的项目中的问题的解决方案是使接口本身通用,即Int1<X>(当然我使用比这更好的名称,它只是一个例子)。

6 个答案:

答案 0 :(得分:7)

我怀疑,编译器应该警告未经检查的转换,而不是将其标记为编译器错误。让我们理解为什么它会警告你:

您需要执行以下两种方法来满足您实现的接口合同:

public List getList();
public List<Integer> getList();

现在,如果在您的类中,您只提供第一个方法的实现,它可以处理2 nd 方法的请求。您可以返回List<Integer>,其返回类型为List。这是因为List<Integer>在运行时只是List,因为类型擦除

但是,如果你只是给出2 nd 方法实现,它将不满足第一种方法的契约。第一种方法说,它可以返回任何类型的List,因为它在返回类型中使用了原始类型。因此,它可以返回List<Integer>List<Object>List<String>,任何内容。但是2 nd 方法只能返回List<Integer>

编译器将在编译时执行此类型检查,它会向您发出警告,List需要取消选中转换为List<Integer>,因为由于类型擦除,转换无论如何都会在运行时成功。 / p>


这是类似的情况:

List<String> listString = new ArrayList<String>();
List rawList = new ArrayList();

listString = rawList;  // Warning: Unchecked conversion
rawList = listString;

推荐阅读

答案 1 :(得分:5)

答案在JLS 7 5.5.1. Reference Type Casting

  

给定编译时引用类型S(源)和编译时   引用类型T(目标),如果存在从S到T的转换转换   由于以下规则,不会发生编译时错误。

     

如果S是班级类型:

If T is a class type, then either |S| <: |T|, or |T| <: |S|. Otherwise, a compile-time error occurs.

Furthermore, if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types
     

(§4.5),并且X和Y的擦除是相同的,编译时   发生错误。

在您的情况下,List<Integer>List<String>是可证明不同的参数化类型,并且它们都具有相同的删除:List

答案 2 :(得分:4)

public List<Integer> getList()的签名与类型擦除后public List getList()的签名相同。对于重写方法,您需要覆盖方法作为重写方法的子签名。如果类型擦除后它们是相同的,那么这里的编译器将决定子类方法是否覆盖接口,并且我们永远不会发生冲突。

答案 3 :(得分:4)

这实际上是因为类型擦除而没有效果:

public interface Int1 {
     public List getList();
}

public interface Int2 extends Int1 {
     @Override
     public List<Integer> getList();
}

在运行时,任何List<X>都会变为List。因此,你不要在这里@Override做任何事情。 .getList()的运行时原型是List getList()。您在List中参数化Int2这一事实完全被忽略了。

编译器警告你:

java: Note: Main.java uses unchecked or unsafe operations.
java: Note: Recompile with -Xlint:unchecked for details.

答案 4 :(得分:2)

Int2扩展Int1然后List就可以了,

List<String> 

无法编译,因为它未在Int1或Int2中定义。

List<Integer> 

in

Int2 

根据Java规范http://docs.oracle.com/javase/tutorial/java/generics/erasure.html

进行类型擦除

答案 5 :(得分:0)

所以看起来它与类型擦除有关,它产生警告而不是编译器错误。我想这就像我之前见过的其他未经检查的转换场景。感谢人们研究得很好的答案。