Issue
Context
I have an interface Job
which contains a single method as shown below.
@FunctionalInterface
public interface Job {
public void run() throws Exception;
}
In one of my classes, I have a list of these Jobs
like so:
class MyClass<A, B, C, D, E, F> { // I know, it's a large number of generic types
transient final ArrayList<Job> hooks;
/* Constructor */
public MyClass(){
hooks = new ArrayList<>();
}
}
Problem
From another class in the same package as MyClass
, I am trying to loop over the contents of member hooks
like so:
class DifferentClass {
@SuppressWarnings("rawtypes")
void procedure(MyClass a){
for (final Job h : a.hooks) { // Compilation error here
try {
h.run();
} catch (Exception e) {
}
}
}
But Eclipse compiler doesn't let me do that. I obtain the following error:
Type mismatch: cannot convert from element type Object to Job
However, if the above loop is within a method of class MyClass
then the compiler doesn't complain:
class MyClass<A, B, C, D, E, F> { // I know, it's a large number of generic types
transient final ArrayList<Job> hooks;
void procedureInClass() {
for (final Job h : hooks) { // No compilation error in this case
try {
h.run();
} catch (Exception e) {
}
}
}
/* Constructor */
public MyClass(){
hooks = new ArrayList<>();
}
}
To make matter more confusing, if I compile my project from the command line using maven
and OpenJDK v1.8.0_312
, my project compiles without problem in both cases.
Question
What is preventing from looping over the contents of member MyClass#hooks
from outside that class within the Eclipse IDE?
(edit) Following very helpful comments I added the generic types / @SuppressWarnings
that I originally omitted.
Solution
This is because by declaring a
as MyClass
without the generic specification, you are declaring it as a raw type
Generally we are used to List<SomeType>
being generic, that at run-time undergoes type-erasure. Further, that if we have old code like :
List myRawTypeVariable = new ArrayList();
that myRawType
is a raw type, in that we can add any Object
and only get Object
s out.
However, it turns out that (as you have discovered) raw types go further than that, and type-erasure has compile-time implications as well.
The Java Language Specification (JLS) says this (source : https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.8 )
The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.
Note that this is NOT limiting type erasure to ONLY those of the generic type; The types of ALL instance methods get taken to their raw types !
In other words, by not specifying the generic type of MyClass
, you are making MyClass
a raw type - and therefore turning off all Generic type-checking for that class (except for methods, etc otherwise specified, from inheritance or interfaces).
So even though hooks
is declared as being a List<Job>
, because you are using MyClass
as a raw-type, it's dropping the <Job>
generic and treating that method as simply returning a List
(effectively a List<Object>
).
So the fix is to add the generic specifier, so :
void procedure(MyClass<?> a){
Answered By - racraman
Answer Checked By - Timothy Miller (JavaFixing Admin)