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?
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
/* ... */
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With