简体   繁体   English

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

[英]Java generics with wildcard compile in Eclipse, but not in javac

As a follow up to Java generics compile in Eclipse, but not in javac , I post another snippet which compiles and runs fine in Eclipse, but raises a compilation error in javac. 作为Java泛型在Eclipse中而不是在javac中进行编译的后续措施,我发布了另一个代码段,该代码段在Eclipse中可以编译并正常运行,但是会在javac中引发编译错误。 (This prevents the project the snippet is extracted from, from being build with Maven.) (这可以防止使用Maven构建摘录片段的项目。)

The self-contained snippet: 独立的代码段:

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;
    }
  }
}

Compilation in javac returns: 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);
                                    ^

On substitution of Foo<?> with Foo<String> the above snippet will compile in javac, which means the problem is related to the used wildcard. Foo<String>替换Foo<?> ,以上代码段将在javac中编译,这意味着问题与使用的通配符有关。 As the Eclipse compiler is supposed to be more tolerant, is it possible the snippet is no valid Java? 由于Eclipse编译器应该更宽容,因此代码段是否可能不是有效的Java?

(I use javac 1.6.0_37 and Eclipse Indigo with compiler compliance level 1.6) (我使用javac 1.6.0_37和Eclipse Indigo,编译器符合级别1.6)

( EDIT1: Included another example which got removed in EDIT2.) EDIT1:包括另一个示例,该示例已在EDIT2中删除。)

EDIT2: Hinted by irreputable , that comparing Foo<A> and Foo<B> may be conceptually wrong, and inspired by the answer of seh , a working asSortedFooList can be written as follows: 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;
}

(Simple substitution of Comparable<T> with Foo<?> in the method definition above.) So it seems to be safe for javac and imho conceptually right to compare any Foo<A> and Foo<B> . (在上面的方法定义中,用Foo<?>替换了Comparable<T> 。)因此,javac和imho在概念上比较任何Foo<A>Foo<B>似乎都是安全的。 But it is still not possible to write a generic method asSortedList which returns a sorted list representation for a generic collection, if its type argument is parametrized with a wildcard. 但是,仍然无法编写通用方法asSortedList ,如果它的类型参数使用通配符进行参数化,则该方法将返回通用集合的排序列表表示形式。 I tried to "trick" javac by substituting Foo<?> by S extends Comparable<S> in asSortedFooList , but this didn't work. 我试图用S extends Comparable<S>代替Foo<?>来“欺骗” javac,并在asSortedFooList S extends Comparable<S> ,但这是行不通的。

EDIT3: Later Rafaelle pointed out, that there is a flaw in the design, since implementing Comparable<Foo<T>> is not necessary, and implementing Comparable<Foo<?>> provides the same functionality, solving the initial problem by refined design. EDIT3:后来Rafaelle指出,由于没有必要实现Comparable<Foo<T>> ,并且实现Comparable<Foo<?>>提供了相同的功能,因此设计上存在缺陷,可以通过精细设计解决初始问题。

(The initial reason and benefit was, that a Foo<T> may not care in some purposes about its concrete type but still use an instance of a concrete type T , it is instantiated with, for other purposes. That instance does not have to be used for determining the order among other Foo s, as it may be used in other parts of the API. (最初的原因和好处是, Foo<T> 在某些方面可能并不关心其具体类型,但仍将其实例化为具体类型T实例用于其他目的。该实例不必用于确定其他Foo之间的顺序,因为它可以在API的其他部分中使用。

Concrete example: Assume each Foo is instantiated with a different type argument for T . 具体示例:假设每个Foo都使用T的不同类型参数实例化。 Every instance of Foo<T> has an incrementing id of type int which is used in the implementation of the compareTo -method. Foo<T>每个实例都有一个int类型的递增ID,该ID在compareTo方法的实现中使用。 We can now sort a list of these differently typed Foo and don't care about the concrete type T (expressing it with Foo<?> ) and still have an instance of a concrete type T accessible for later processing.) 现在,我们可以对这些类型不同的Foo排序,而不必关心具体的类型T (用Foo<?> ),并且仍然可以访问具体的类型T的实例,以便以后进行处理。)

In this case, javac is correct. 在这种情况下,javac是正确的。 Conceptually, your code can't work, since the set may contain Foo<A> and Foo<B> , which can't be compared to each other. 从概念上讲,您的代码无法运行,因为该集合可能包含Foo<A>Foo<B> ,它们无法相互比较。

You probably want the set to be a Set<Foo<X>> for some type variable X; 您可能希望将集合Set<Foo<X>>某个类型变量X的Set<Foo<X>> unfortunately we can't introduce type variable inside method body; 不幸的是我们不能在方法体内引入类型变量。 only in method signature 仅在方法签名中

<X> void test(){
    Set<Foo<X>> setOfFoos = new HashSet<Foo<X>>();
    List<Foo<X>> sortedListOfFoos = asSortedList(setOfFoos);
}

You may make it work by something like 您可以通过类似的方法使其工作

<T extends Comparable<? super T>> List<T> asSortedList(Collection<T> c) 


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

To me this is another javac bug. 对我来说,这是另一个javac错误。 When you try to send a Collection<Foo<?>> to a method with the signature: 当您尝试将Collection<Foo<?>>发送到带有签名的方法时:

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

the compiler notes that the formal parameter T has an upper bound, so checks if the constrained is honored by the caller. 编译器注意到形式参数 T有一个上限,因此请检查约束条件是否被调用方接受。 The type argument is a (wildcard) instantiation of the parameterized type Foo<T> , so the test will pass if Foo<?> is-a Comparable<Foo<?>> . type参数是参数化类型Foo<T>的(通配符)实例化,因此如果Foo<?> Comparable<Foo<?>> ,则测试将通过。 Based upon the generic definition: 根据通用定义:

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

I'd say that it's true, so again Eclipse is right and javac has a bug. 我会说这是真的,因此Eclipse再一次是正确的,而javac有一个错误。 This Angelika Langer's entry is never linked enough. Angelika Langer的条目从来没有足够的关联。 Also see the relevant JLS . 另请参阅相关的JLS

You asked if it is type-safe or not. 您询问它是否是类型安全的。 My answer is that it is type safe , and it shows you have a flaw in your design. 我的回答是,它是类型安全的 ,它表明您的设计存在缺陷。 Consider your fictitious implementation of the Comparable<T> interface, where I added two more fields: 考虑您对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;
  }
}

You always return 0 , so the problem is not spotted. 您始终返回0 ,因此不会发现问题。 But when you try to make it useful, you have two options: 但是,当您尝试使其有用时,您有两种选择:

  1. Comparing on the String field 比较字符串字段
  2. Comparing on the T member T成员进行比较

The String field is always a String , so you don't really benefit from the type variable T . String字段始终是一个String ,因此您不会真正受益于类型变量T On the other hand, T has no other type information available, so in compareTo() you can only deal with a plain object, and again the type parameter is useless. 另一方面, T没有其他可用的类型信息,因此,在compareTo()您只能处理一个普通对象,并且type参数同样无用。 You can achieve the same exact functionality by implementing Comparable<Foo<?>> 通过实现Comparable<Foo<?>>您可以实现相同的确切功能。

I don't know if this is a question, but here is a (not very nice) answer: If you sacrifice some type safety you can write 我不知道这是否是一个问题,但这是一个(不是很好)答案:如果您牺牲了某些类型安全性,则可以编写

@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;
}

And it works in both eclipse and javac. 它在eclipse和javac中均可使用。 The only risk that I'm aware of is that if someone creates a class Foo extends Comparable<Bazz> you won't detect that in compile time. 我知道的唯一风险是,如果有人创建class Foo extends Comparable<Bazz>您不会在编译时检测到它。 But if someone creates Foo extends Comparable<Bazz> , just kill him/her. 但是,如果有人创建Foo extends Comparable<Bazz> ,则杀死他/她。

I found a solution that compiles with javac , though I am not happy that I am unable to explain exactly why it works. 我发现可以用javac编译的解决方案,尽管我不满意无法确切解释其工作原理,对此我感到不满意。 It requires introducing an intermediary function: 它需要引入一个中介功能:

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);
  }
}

I think that this works by virtue of taking the wildcard resolution step-by-step; 认为这是通过逐步执行通配符解析来实现的; asSortedFooList() captures one type known to be a Foo , irrespective of Foo 's type parameter. asSortedFooList()捕获一种类型的已知为Foo ,不论Foo的类型参数。 With that type parameter bound in asSortedFooList() , we can then call on your original asSortedList() (well, with one modification—note the lower bound on the type parameter for Comparable ) requiring binding Foo as a type descended from Comparable . 将该类型参数绑定到asSortedFooList() ,我们可以调用原始的asSortedList() (需要做一个修改-请注意Comparable的type参数的下限),需要将Foo绑定为从Comparable继承的类型。

Again, that's a weak, haphazard explanation. 同样,这是一个微不足道的,偶然的解释。 My main point in answering here is just to provide one more way to get to your destination. 我在这里回答的主要目的只是提供另一种到达目的地的方式。

If you can replace your wildcard usage with an exact type (which may be a super-type) your code will work. 如果您可以将通配符用法替换为确切的类型(可能是超类型),则代码将起作用。 Replace 更换

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

with

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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