[英]Java generics with wildcard compile in Eclipse, but not in 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;
}
(在上面的方法定义中,用Foo<?>
替换了Comparable<T>
。)因此,javac和imho在概念上比较任何Foo<A>
和Foo<B>
似乎都是安全的。 但是,仍然无法编写通用方法asSortedList
,如果它的类型参数使用通配符进行参数化,则该方法将返回通用集合的排序列表表示形式。 我试图用S extends Comparable<S>
代替Foo<?>
来“欺骗” javac,并在asSortedFooList
S extends Comparable<S>
,但这是行不通的。
EDIT3:后来Rafaelle指出,由于没有必要实现Comparable<Foo<T>>
,并且实现Comparable<Foo<?>>
提供了相同的功能,因此设计上存在缺陷,可以通过精细设计解决初始问题。
(最初的原因和好处是, Foo<T>
在某些方面可能并不关心其具体类型,但仍将其实例化为具体类型T
实例用于其他目的。该实例不必用于确定其他Foo
之间的顺序,因为它可以在API的其他部分中使用。
具体示例:假设每个Foo都使用T
的不同类型参数实例化。 Foo<T>
每个实例都有一个int
类型的递增ID,该ID在compareTo
方法的实现中使用。 现在,我们可以对这些类型不同的Foo
排序,而不必关心具体的类型T
(用Foo<?>
),并且仍然可以访问具体的类型T
的实例,以便以后进行处理。)
在这种情况下,javac是正确的。 从概念上讲,您的代码无法运行,因为该集合可能包含Foo<A>
和Foo<B>
,它们无法相互比较。
您可能希望将集合Set<Foo<X>>
某个类型变量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<?>>
对我来说,这是另一个javac
错误。 当您尝试将Collection<Foo<?>>
发送到带有签名的方法时:
public static <T extends Comparable<T>> List<T> asSortedList(Collection<T> c)
编译器注意到形式参数 T
有一个上限,因此请检查约束条件是否被调用方接受。 type参数是参数化类型Foo<T>
的(通配符)实例化,因此如果Foo<?>
是 Comparable<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
,因此不会发现问题。 但是,当您尝试使其有用时,您有两种选择:
T
成员进行比较 String
字段始终是一个String
,因此您不会真正受益于类型变量T
另一方面, T
没有其他可用的类型信息,因此,在compareTo()
您只能处理一个普通对象,并且type参数同样无用。 通过实现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
的type参数的下限),需要将Foo
绑定为从Comparable
继承的类型。
同样,这是一个微不足道的,偶然的解释。 我在这里回答的主要目的只是提供另一种到达目的地的方式。
如果您可以将通配符用法替换为确切的类型(可能是超类型),则代码将起作用。 更换
List<Foo<?>> sortedListOfFoos = asSortedList(setOfFoos);
与
List<Foo<String>> sortedListOfFoos = Main.<Foo<String>>asSortedList(setOfFoos);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.