10

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

Community
  • 1
  • 1
mtsz
  • 2,725
  • 7
  • 28
  • 41

5 Answers5

2

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<?>> 
irreputable
  • 44,725
  • 9
  • 65
  • 93
  • Please see my proposed solution which does just that: it introduces a type variable for `Foo` using an otherwise unnecessary intermediary method. – seh Nov 23 '12 at 21:22
  • wildcard capture only works on 1st level; in this example the wildcard is on the 2nd level. – irreputable Nov 23 '12 at 21:25
  • If Eclipse compiles it, there must be a rationale, and if we find it we can get `javac` to compile too. It's not a compiler's purpose to tell if it's conceptually right or wrong: I find misleading to use human concepts like *set* and *contain* when explaining generics. The compiler is a dumb program that can only check if a sequence of tokens follows the Java rules. It doesn't understand semantics like *container type* and *contains* – Raffaele Nov 23 '12 at 23:23
  • I'm also a dumb program that can check if the code follows language spec. I can also reason that the code violates type safety, so it can't work, since Java is type safe. – irreputable Nov 23 '12 at 23:30
  • @irreputable I've edited the question to discuss the type (un)safety of such a definition. I can see why it is type safe to prohibit such definitions, but I am not able to see right now where it gets unsafe when you allow them (meaning it may be to strict). – mtsz Nov 24 '12 at 00:15
  • I never said you are wrong, just wanted to encourage you to improve your answer – Raffaele Nov 24 '12 at 00:19
2

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

Raffaele
  • 20,627
  • 6
  • 47
  • 86
  • Thanks for your insights. I will study the JLS and Ms. Langer's explanation, thanks for linking to it. Regarding your argument about the flawed design: I've updated my question with further information and a concrete example, why such a design may have a benefit (at least for me). – mtsz Nov 24 '12 at 19:00
  • 2
    Maybe *flawed design* is too much. I'd have better said *suboptimal*. The point is you can't do anything useful with the `T` variable inside `compareTo()`, so you can safely implement `Comparable>` and javac will be happy to compile :) – Raffaele Nov 24 '12 at 19:12
  • You are right, it is a flaw, I didn't read it carefully enough, especially your last line! Implementing `Comparable>` instead of `Comparable>` does the job, I've should have seen it earlier, but didn't :) – mtsz Nov 24 '12 at 19:17
  • I would like to accept your answer, because it provided useful insights about the initial problem and solved my design flaw. – mtsz Nov 24 '12 at 19:21
  • I don't think you have to ask permission to accept an answer :P – Raffaele Nov 24 '12 at 19:22
1

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.

Pablo Grisafi
  • 5,039
  • 1
  • 19
  • 29
  • 1
    My first question is, if it is valid Java, and if not, any analogous solution is welcome. Your solution compiles, and has the risk you have foreseen. The class can be made final, so nobody should get harmed :) – mtsz Nov 23 '12 at 21:16
1

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.

seh
  • 14,999
  • 2
  • 48
  • 58
  • @seh If I understand your solution right, you substituted `main` and added the method `asSortedFooList`, leaving the rest of my snippet unaltered? If I do as described, I get in Eclipse an compilation error `Bound mismatch: The generic method asSortedList(Collection) of type Main is not applicable for the arguments (Collection). The inferred type T is not a valid substitute for the bounded parameter >`. javac does not work as well. Would you please provide your complete solution? Thanks! – mtsz Nov 23 '12 at 22:59
  • 1
    Sorry about that. I neglected to include the slightly-modified `asSortedList()` with a newly-added lower bound on its type parameter. Please try the additional code included in my answer and let us known if Eclipse accepts it. – seh Nov 24 '12 at 01:46
  • Yes, Eclipse and its compiler EJC did accept it, thanks. Inspired by your answer, I managed to make my solution compile by applying `T extends Foo>` to the signature of `asSortedList` directly, which works similar to your solution but using only one method. I've edited my question to provide additional information. – mtsz Nov 24 '12 at 18:57
0

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);
Trygve Laugstøl
  • 7,440
  • 2
  • 36
  • 40
  • Giving -1 without a comment is lame. – Trygve Laugstøl Nov 23 '12 at 20:58
  • I am not dovnwoter but you probably got -1 because your answer doesn't solve OP problem (I may be mistaken since I just tested it with Java 7 compilator and OP is using version 6) – Pshemo Nov 23 '12 at 21:00
  • I don't get it, he wanted the code to compile and it does make it compile. – Trygve Laugstøl Nov 23 '12 at 21:01
  • I've tried your proposed approach before, as it was provided in the linked question already. Unfortunately it does not work in this case. Your version of IDEA uses javac 1.6.x? – mtsz Nov 23 '12 at 21:10
  • There is normally a difference of explicitly giving the arguments so it was not exactly as given. But it was still not a correct answer. The problem is with the wildcard operator. Change that to String and it works. I even tried with a manual javac command! :) – Trygve Laugstøl Nov 23 '12 at 21:15
  • Thanks, I've noticed that it is the wildcard. I've mentioned it at the end of the question, as I did the same test as you :) That is what puzzles me, because I don't see why the wildcard does have such an impact. – mtsz Nov 23 '12 at 21:22