10

作为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<?>使用上述代码段替换时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>with 。)因此 javac 和 imho 在概念上比较任何andFoo<?>似乎是安全的。但是,如果它的类型参数是用通配符参数化的,那么仍然不可能编写一个返回泛型集合的排序列表表示的泛型方法。我试图通过替换 in 来“欺骗”javac ,但这没有用。Foo<A>Foo<B>asSortedListFoo<?>S extends Comparable<S>asSortedFooList

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

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

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

4

5 回答 5

2

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

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

编译器注意到形式参数 T有一个上限,因此检查调用者是否遵守约束。type 参数是参数化 type 的(通配符)实例化,因此如果is-aFoo<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字段始终是 a String,因此您并没有真正从类型变量中受益T。另一方面,T没有其他可用的类型信息,因此compareTo()您只能处理普通对象,并且类型参数再次无用。您可以通过实现相同的功能Comparable<Foo<?>>

于 2012-11-24T16:48:52.960 回答
2

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

您可能希望该集合是Set<Foo<X>>某个类型变量 X 的 a;不幸的是,我们不能在方法体内引入类型变量;仅在方法签名中

<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<?>> 
于 2012-11-23T21:17:13.393 回答
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()捕获一种已知为 a的类型Foo,与Foo的类型参数无关。将该类型参数绑定在 中asSortedFooList(),然后我们可以调用您的原始类型asSortedList()(嗯,进行一次修改——注意类型参数的下限 for Comparable),要求绑定Foo为继承自 的类型Comparable

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

于 2012-11-23T21:16:12.363 回答
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。我知道的唯一风险是,如果有人创建 aclass Foo extends Comparable<Bazz>你不会在编译时检测到它。但如果有人创造Foo extends Comparable<Bazz>,就杀了他/她。

于 2012-11-23T20:39:48.363 回答
0

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

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

List<Foo<String>> sortedListOfFoos = Main.<Foo<String>>asSortedList(setOfFoos);
于 2012-11-23T20:33:01.680 回答