Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Omitting <?> unintuitively breaks this code

I have created a MWE where changing a single line by adding <?> solves a compiler error.

The following code does not compile:

import java.util.List;

public class MainClass {

    public void traverse() {
        List<MyEntity> list = null /* ... */;
        for (MyEntity myEntity : list) {
            for (String label : myEntity.getLabels()) { // <-- Offending Line
                /* ... */
            }
        }
    }

    interface MyEntity<T> {
        T get();

        List<String> getLabels();
    }
}

The compiler error is:

Error:(9, 51) java: incompatible types: java.lang.Object cannot be converted to java.lang.String

Changing the definition in the offending line from MyEntity myEntity to MyEntity<?> myEntity solves the issue. I wonder why is the return type of this for-each treated as an Object and not as a String, unless I add the wildcard to the parent class? Note that getLabels() itself does not contain generics.

According to Chapter 14.14.2. of the Java Language Specification, a for-each is compiled to a loop using an iterator. Interestingly, expanding the for-each to such an iterator manually works:

Iterator<String> iterator = myEntity.getLabels().iterator();
while (iterator.hasNext()) {
    String label = iterator.next();
    /* ... */
}

Can anyone explain why?

like image 824
Michael Borkowski Avatar asked Feb 15 '17 10:02

Michael Borkowski


1 Answers

First of all, method body of your code example can be simplified to:

public void traverse() {
    MyEntity myEntity = null;
    for (String label : myEntity.getLabels()) { // <--  Offending Line
            /* ... */
    }
}

Why is that happening? Because when you declare variable myEntity (it doesn't matter where - in for-loop or as in my example) as MyEntity myEntity, you declare it as raw type, which eliminates generic type from return type of method getLabels as well: so it becomes just like List getLabels();, where obviously Object type is expected for for-loop construction.

In the same time Iterator<String> iterator = myEntity.getLabels().iterator(); works fine, because you are specifying type here explicitly: Iterator<String>.


Very similar example is given in JLS 4.8 "Raw types" which explains why does it happen:

...Inherited type members that depend on type variables will be inherited as raw types as a consequence of the rule that the supertypes of a raw type are erased...

Another implication of the rules above is that a generic inner class of a raw type can itself only be used as a raw type:

class Outer<T>{
     class Inner<S> {
         S s;
     }
}

It is not possible to access Inner as a partially raw type (a "rare" type):

Outer.Inner<Double> x = null; // illegal

UPD-2: As I received questions about Iterator<String> iterator = myEntity.getLabels().iterator();, why does it ok to do so, while first example doesn't work?

I personally agree that it looks confusing. But such are the rules. This case is also covered in same JLS paragraph with this example:

class Cell<E> {
    E value;

    Cell(E v)     { value = v; }
    E get()       { return value; }
    void set(E v) { value = v; }

    public static void main(String[] args) {
        Cell x = new Cell<String>("abc");
        System.out.println(x.value);  // OK, has type Object
        System.out.println(x.get());  // OK, has type Object
        x.set("def");                 // unchecked warning
    }
}

More careful explanation about why does Iterator<String> iterator = myEntity.getLabels().iterator(); work from JLS are based on this rule:

That is, the subtyping rules (§4.10.2) of the Java programming language make it possible for a variable of a raw type to be assigned a value of any of the type's parameterized instances

In same way you can always write well-compiled code as:

    List<String> labels = myEntity.getLabels();
    for (String label : labels) { // <-- OK, no error here
            /* ... */
    }
like image 78
Andremoniy Avatar answered Nov 10 '22 11:11

Andremoniy