在Eclipse中使用通配符编译的Java泛型,但不是在javac中

时间:2012-11-23 19:45:27

标签: java eclipse generics wildcard javac

作为Java generics compile in Eclipse, but not in javac的后续内容,我发布了另一个在Eclipse中编译并运行良好的代码片段,但在javac中引发了编译错误。 (这可以防止从Maven构建提取代码段的项目。)

自包含代码段:

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


public class Main {
  public static void main(String[] args) {
    Set<Foo<?>> setOfFoos = new HashSet<Foo<?>>();
    List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
  }


  public static <T extends Comparable<T>> List<T> asSortedList(Collection<T> c) {
    List<T> list = new ArrayList<T>(c);
    java.util.Collections.sort(list);
    return list;
  }


  public static class Foo<T> implements Comparable<Foo<T>> {
    @Override
    public int compareTo(Foo<T> o) {
      return 0;
    }
  }
}

javac中的编译返回:

Main.java:11: <T>asSortedList(java.util.Collection<T>) in Main cannot be applied to (java.util.Set<Main.Foo<?>>)
    List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
                                    ^

在用Foo<?>替换Foo<String>时,上面的代码片段将在javac中编译,这意味着该问题与使用的通配符有关。由于Eclipse编译器应该更宽容,因此代码段可能不是有效的Java吗?

(我使用javac 1.6.0_37和Eclipse Indigo,编译器合规级别为1.6)

EDIT1:包含在EDIT2中删除的另一个示例。)

EDIT2: irreputable暗示,比较Foo<A>Foo<B>可能在概念上是错误的,并且受到seh答案的启发,工作asSortedFooList可以写成如下:

public static <T extends Foo<?>> List<T> asSortedFooList(Collection<T> c) {
    List<T> list = new ArrayList<T>(c);
    java.util.Collections.sort(list);
    return list;
}

(在上面的方法定义中用Comparable<T>简单替换Foo<?>。) 因此,javac和imho在概念上比较任何Foo<A>Foo<B>似乎是安全的。但是仍然不可能编写泛型方法asSortedList,如果其类型参数使用通配符进行参数化,则返回泛型集合的排序列表表示。我试图通过在Foo<?>中用S extends Comparable<S>代替asSortedFooList来“欺骗”javac,但这不起作用。

EDIT3:后来Rafaelle指出,设计存在缺陷,因为没有必要实施Comparable<Foo<T>>,并且实施Comparable<Foo<?>>提供了相同的功能,通过精细设计解决初始问题。

(最初的原因和好处是,Foo<T>可能不关心在某些方面关于其具体类型但仍然使用具体类型T的实例,它被实例化,用于其他目的。该实例不必用于确定其他Foo之间的顺序,因为它可以在API的其他部分中使用。

具体示例:假设每个Foo都使用T的不同类型参数进行实例化。 Foo<T>的每个实例都有一个int类型的递增ID,用于实现compareTo - 方法。我们现在可以对这些不同类型Foo的列表进行排序,而不关心具体类型T(用Foo<?>表示)和仍然有一个具体类型T的实例,可供以后处理。)

5 个答案:

答案 0 :(得分:2)

在这种情况下,javac是正确的。从概念上讲,您的代码无法使用,因为该集可能包含Foo<A>Foo<B>,但无法相互比较。

对于某些类型变量X,您可能希望该集合为Set<Foo<X>>;遗憾的是我们不能在方法体内引入类型变量;仅在方法签名

<X> void test(){
    Set<Foo<X>> setOfFoos = new HashSet<Foo<X>>();
    List<Foo<X>> sortedListOfFoos = asSortedList(setOfFoos);
}

您可以通过类似

的方式使其工作
<T extends Comparable<? super T>> List<T> asSortedList(Collection<T> c) 


class Foo<T> implements Comparable<Foo<?>> 

答案 1 :(得分:2)

对我而言,这是另一个javac错误。当您尝试将Collection<Foo<?>>发送到带有签名的方法时:

public static <T extends Comparable<T>> List<T> asSortedList(Collection<T> c)

编译器注意到形式参数 T具有上限,因此检查调用者是否遵守约束。 类型参数是参数化类型Foo<T>的(通配符)实例化,因此如果Foo<?> 是-a {{1},测试将通过}}。基于通用定义:

Comparable<Foo<?>>

我会说这是真的,所以Eclipse再次正确,class Foo<T> implements Comparable<Foo<T>> 有一个错误。这个Angelika Langer's entry永远不够联系。另请参阅the relevant JLS

您询问它是否属于类型安全。我的回答是是类型安全的,它表明你的设计存在缺陷。考虑一下javac界面的虚构实现,我在其中添加了两个字段:

Comparable<T>

您总是返回public static class Foo<T> implements Comparable<Foo<T>> { private T pState; private String state; @Override public int compareTo(Foo<T> other) { return 0; } } ,因此未发现问题。但是当你试图让它变得有用时,你有两个选择:

  1. 比较字符串字段
  2. 0成员
  3. 上进行比较

    T字段始终为String,因此您不会真正受益于类型变量String。另一方面,T没有其他可用的类型信息,因此在T中,您只能处理普通对象,而类型参数也是无用的。您可以通过实施compareTo()

    来实现相同的功能

答案 2 :(得分:1)

我不知道这是不是一个问题,但这里有一个(不是很好)答案: 如果你牺牲一些类型安全,你可以写

@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T extends Comparable> List<T> asSortedList(Collection<T> c) {
    List<T> list = new ArrayList<T>(c);
    java.util.Collections.sort(list);
    return list;
}

它适用于eclipse和javac。我所知道的唯一风险是,如果有人创建class Foo extends Comparable<Bazz>,您将无法在编译时检测到它。 但如果有人创建Foo extends Comparable<Bazz>,就要杀死他/她。

答案 3 :(得分:1)

我找到了一个用 javac 编译的解决方案,但我不高兴我无法解释其确切原因。它需要引入中介功能:

public final class Main {
  public static class Foo<T> implements Comparable<Foo<T>> {
    @Override
    public int compareTo(Foo<T> o) {
      return 0;
    }
  }


  public static <T extends Comparable<? super T>>
  List<T> asSortedList(Collection<T> c) {
    final List<T> list = new ArrayList<T>(c);
    java.util.Collections.sort(list);
    return list;
  }


  private static <T extends Foo<?>> List<T> asSortedFooList(Collection<T> c) {
    return asSortedList(c);
  }


  public static void main(String[] args) {
    final Set<Foo<?>> setOfFoos = new HashSet<Foo<?>>();
    final List<Foo<?>> listOfFoos = asSortedFooList(setOfFoos);
  }
}

认为这是通过逐步采用通配符解决方案来实现的;无论asSortedFooList()的类型参数如何,Foo都会捕获一个已知为Foo的类型。使用asSortedFooList()中绑定的类型参数,我们可以调用您的原始asSortedList()(以及一个修改 - 注意Comparable的类型参数的下限),这需要绑定{{} 1}}作为Foo的后代类型。

同样,这是一个弱的,随意的解释。我在这里回答的主要观点是提供一种到达目的地的方式。

答案 4 :(得分:0)

如果您可以使用精确类型(可能是超类型)替换通配符用法,则代码将起作用。取代

List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);

List<Foo<String>> sortedListOfFoos = Main.<Foo<String>>asSortedList(setOfFoos);