Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 Foos, 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.)

like image 506
mtsz Avatar asked Nov 23 '12 19:11

mtsz


2 Answers

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<?>>

like image 107
Raffaele Avatar answered Oct 12 '22 01:10

Raffaele


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<?>> 
like image 37
irreputable Avatar answered Oct 12 '22 01:10

irreputable