作为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
的实例,可供以后处理。)
答案 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;
}
}
,因此未发现问题。但是当你试图让它变得有用时,你有两个选择:
0
成员 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);