简体   繁体   中英

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. (This prevents the project the snippet is extracted from, from being build with 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:

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. As the Eclipse compiler is supposed to be more tolerant, is it possible the snippet is no valid Java?

(I use javac 1.6.0_37 and Eclipse Indigo with compiler compliance level 1.6)

( EDIT1: Included another example which got removed in 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:

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> . 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. I tried to "trick" javac by substituting Foo<?> by S extends Comparable<S> in asSortedFooList , but this didn't work.

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.

(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.

Concrete example: Assume each Foo is instantiated with a different type argument for T . Every instance of Foo<T> has an incrementing id of type int which is used in the implementation of the compareTo -method. 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.)

In this case, javac is correct. Conceptually, your code can't work, since the set may contain Foo<A> and Foo<B> , which can't be compared to each other.

You probably want the set to be a Set<Foo<X>> for some type variable 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. When you try to send a Collection<Foo<?>> to a method with the signature:

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. The type argument is a (wildcard) instantiation of the parameterized type Foo<T> , so the test will pass if Foo<?> is-a 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. This Angelika Langer's entry is never linked enough. Also see the relevant 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:

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. But when you try to make it useful, you have two options:

  1. Comparing on the String field
  2. Comparing on the T member

The String field is always a String , so you don't really benefit from the type variable 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. You can achieve the same exact functionality by implementing 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. 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. But if someone creates Foo extends Comparable<Bazz> , just kill him/her.

I found a solution that compiles with javac , though I am not happy that I am unable to explain exactly why it works. 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. 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 .

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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