繁体   English   中英

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

[英]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 ,因此不会发现问题。 但是,当您尝试使其有用时,您有两种选择:

  1. 比较字符串字段
  2. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM