带有通配符的Java泛型可以在Eclipse中编译,但不能在javac中编译



作为Java泛型在Eclipse中编译(而不是在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<String>替换Foo<?>时,上面的代码段将在javac中编译,这意味着问题与使用的通配符有关。由于Eclipse编译器应该更宽容,所以这个代码片段可能不是有效的Java吗?

(我使用javac 1.6.0_37和Eclipse Indigo,编译器遵从性级别为1.6)

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

EDIT2:无可置疑的是,比较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。我试图通过在asSortedFooList中用S extends Comparable<S>替换Foo<?>来"欺骗"javac,但没有成功。

EDIT3:后来Rafaelle指出,设计中存在缺陷,因为实现Comparable<Foo<T>>是不必要的,而实现Comparable<Foo<?>>提供了相同的功能,通过精细化设计解决了最初的问题。

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

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

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

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

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

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

我想说这是真的,所以Eclipse是对的,javac有一个错误。Angelika Langer的这篇文章从来没有足够的链接。另请参阅相关的JLS。

你问它是否打字安全。我的答案是,它是类型安全的,这表明您的设计有缺陷。考虑一下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;
}
}

您总是返回0,因此不会发现问题。但当你试图让它变得有用时,你有两个选择:

  1. 字符串字段比较
  2. 关于T成员的比较

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

在这种情况下,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<?>> 

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

@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>,就杀了他/她。

我找到了一个用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的类型参数的下界),需要将Foo绑定为Comparable的派生类型。

再说一遍,这是一个软弱、随意的解释。我在这里回答的要点只是提供一条到达目的地的途径。

如果您可以将通配符的用法替换为精确类型(可能是超类型),那么您的代码就会正常工作。更换

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

带有

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

最新更新