4

I have a Problem with a generic method after upgrading to Java 1.8, which was fine with Java 1.6 and 1.7 Consider the following code:

public class ExtraSortList<E> extends ArrayList<E> {
    ExtraSortList(E... elements) {
        super(Arrays.asList(elements));
    }

    public List<E> sortedCopy(Comparator<? super E> c) {
        List<E> sorted = new ArrayList<E>(this);
        Collections.sort(sorted, c);
        return sorted;
    }

    public static void main(String[] args) {
        ExtraSortList<String> stringList = new ExtraSortList<>("foo", "bar");

        Comparator<? super String> compGen = null;
        String firstGen = stringList.sortedCopy(compGen).get(0); // works fine

        Comparator compRaw = null;
        String firstRaw = stringList.sortedCopy(compRaw).get(0); // compiler ERROR: Type mismatch: cannot convert from Object to String
    }
}

I tried this with the Oracle javac (1.8.0_92) and with Eclipse JDT (4.6.1) compiler. It is the same result for both. (the error message is a bit different, but essentially the same)

Beside the fact, that it is possible to prevent the error by avoiding raw types, it puzzles me, because i don't understand the reason.

Why does the raw method parameter of the sortedCopy-Method have any effect on the generic type of the return value? The generic type is already defined at class level. The method does not define a seperate generic type. The reference list is typed to <String>, so should the returned List.

Why does Java 8 discard the generic type from the class on the return value?

EDIT: If the method signature of sortedCopy is changed (as pointed out by biziclop) to

public List<E> sortedCopy(Comparator c) {

then the compiler does consider the generic type E from the type ExtraSortList<E> and no error appears. But now the parameter c is a raw type and thus the compiler cannot validate the generic type of the provided Comparator.

EDIT: I did some review of the Java Language Specification and now i think about, whether i have a lack of understanding or this is a flaw in the compiler. Because:

  • Scope of a Declaration of the generic type E is the class ExtraSortList, this includes the method sortedCopy.
  • The method sortedCopy itself does not declare a generic type variable, it just refers to the type variable E from the class scope. see Generic Methods in the JLS
  • The JLS also states in the same section

    Type arguments may not need to be provided explicitly when a generic method is invoked, as they can often be inferred (§18 (Type Inference)).

  • The reference stringList is defined with String, thus the compiler does not need to infer a type forE on the invocation of sortedCopy because it is already defined.
  • Because stringList already has a reified type for E, the parameter c should be Comparator<? super String> for the given invocation.
  • The return type should also use the already reified type E, thus it should be List<String>.

This is my current understanding of how i think the Java compiler should evaluate the invocation. If i am wrong, an explanation why my assumptions are wrong would be nice.

  • 4
    "Where is my mistake?" In the use of raw types. You know the right thing to do, as it's in the example above - add the `` following `Comparator`. – Andy Turner Nov 24 '16 at 12:36
  • 1
    actually both work fine for me. (1.8.0_45) – SomeJavaGuy Nov 24 '16 at 12:40
  • @KevinEsche same for me! – VeryNiceArgumentException Nov 24 '16 at 12:43
  • See [What is a raw type and why shouldn't we use it?](http://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it) – Jesper Nov 24 '16 at 12:46
  • In practice "don't use raw types" is of course an adequate answer but it's still an interesting question to ask what changed in the type inference rules that has caused this to become invalid. (If it is invalid at all.) – biziclop Nov 24 '16 at 12:52
  • By the way, if you remove the ` super E>` type parameter, then it will compile. So it must have something to do with how the two conflicting constraints on `` (the one coming from `list` and the one coming from the method argument `compRaw`) are resolved. – biziclop Nov 24 '16 at 13:01
  • I'm no expert of the JLS, but the [What's new in Java 8](http://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html) page, under *Javac* states `The type rules for equality operators in the Java Language Specification (JLS) Section 15.21 are now correctly enforced by the javac command.` and that [JLS 15.31.3](http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.21.3) section tells about a compile-time error with operand type conversions. Could the two be related and have an effect on the question? – watery Nov 24 '16 at 13:09

1 Answers1

0

To bring an final answer to why this happens:

Like @Jesper mentioned already, you're using raw types when you shouldn't (Especially when using the Generic as type in multiple cases). Since you pass an Comparator without an Generic-Type, there will actually be none. You could think of the E-Generic as null to make it easier. Therefore your code becomes to this:

public List sortedCopy(Comparator c) {
    List sorted = new ArrayList(this);
    Collections.sort(sorted, c);
    return sorted;
}

Now you're attemptig/assuming you get an String from an List without Generics and therefore an Object (hence it's the super-class of everything ).

To the question why the raw-type parameter has no effect on the return type, since you don't specify an certain Level of abstraction. You'd have to define an Type that the Generic has to extend/implement at least to make that happen (compilation errors), for example.

public class ExtraSortList<E extends String> extends ArrayList<E> {

will now only allow Strings or Classes which extend it (not possible here since string is final). With that, your fallback Type would be String.

PreFiXAUT
  • 140
  • 9
  • Your answer makes it clear that the Java compiler precedes the generic type from the method parameter before the generic type that is already present at the called 'list' reference. I will check the JLS for any clue. Because there must be a change between 1.7 and 1.8 – Maik Scheibler Nov 24 '16 at 14:20
  • 1
    @MaikScheibler It could also be that the way the old `javac` handled it didn't match the specification. – biziclop Nov 24 '16 at 16:38