tags:

views:

90

answers:

5

Hi, Could someone please explain to me why there is explicit need to assign generic type for ForEachLoop instance?

Why compiler complains: Type mismatch: cannot convert from element type Object to String?

JDK 1.5.0_09

import java.util.ArrayList;
import java.util.Collection;

public class ForEachLoop<T> {

public static void main(String[] args) {

    // Non functional version
    ForEachLoop f = new ForEachLoop(); 

    // Functional version
    //ForEachLoop<Integer> f = new ForEachLoop();

            // Type mismatch: cannot convert from element type Object to String
    for(String a : f.getStrings()) {
        System.out.println(a);
    }
}

public Collection<String> getStrings() {
    Collection<String> strings = new ArrayList<String>();
    strings.add("Hello");
    return strings;
}

} 
+2  A: 

This is a rather common mistake:

ForEachLoop f = new ForEachLoop(); 

should be

ForEachLoop<Something> f = new ForEachLoop<Something>();

If you use the raw type (which you shouldn't) the compiler will erase all generic information for that instance even if it's not the type parameter T, to make it compatible with pre 1.5 code.

Only use raw types if you're writing for Java 1.4 or less, in which case you shouldn't have any generics whatsoever. At the bytecode level the method returns a Collection (raw) after type erasure. Normally, if the instance has the generic type set, when you try to do get on the collection, the compiler will use the generic information to decide that it should return a String, and then at the bytecode level it automatically casts the Object it receives from the Collection to String (since it's guaranteed to be a String). But if you use the raw type the compiler will ignore all generic information and will not automatically cast the object for you anymore.

Edit: In the section on Raw Types there are these things:

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 partially raw type (a "rare" type)

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

because Outer itself is raw, so are all its inner classes, including Inner, and so it is not possible to pass any type parameters to it.

The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of genericity into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.

It is a compile-time error to attempt to use a type member of a parameterized type as a raw type.

This means that the ban on "rare" types extends to the case where the qualifying type is parameterized, but we attempt to use the inner class as a raw type:

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

This is the opposite of the case we discussed above. There is no practical justification for this half baked type. In legacy code, no type parameters are used. In non-legacy code, we should use the generic types correctly and pass all the required actual type parameters.

Notice that the Inner class has it's own type parameter independent of the one of the Outer class, and it still gets erased. Basically they don't want us mixing raw and generic types on the same instance, since it doesn't make sense in any version (in pre 1.5, the generic part will be an error, in 1.5+ the raw type is discouraged, and may even be removed from future versions)

Then there's also this:

The type of a constructor (§8.8), instance method (§8.8, §9.4), or non-static field (§8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the erasure of its type in the generic declaration corresponding to C. The type of a static member of a raw type C is the same as its type in the generic declaration corresponding to C.

It is a compile-time error to pass actual type parameters to a non-static type member of a raw type that is not inherited from its superclasses or superinterfaces.

which says that constructors, instance methods and non-static fields will be treated as raw in a raw instance. Static members will be treated as generic anyway, since they don't require an instance to be accesed.

Andrei Fierbinteanu
Sounds interesting and logically. Could you please point me to java specification that "compiler will erase all generic information even if it's not the type parameter T"?
Added some snippets from the JLS, and my (perhaps limited) understanding of them.
Andrei Fierbinteanu
I will try to absorb your answer, but it will take me a while. Thank you
A: 

ForEachLoop<String> f = new ForEachLoop<String>();

BobTurbo
A: 

What do you expect would happen in the nonfunctional version? You've declared a template class ForEachLoop<T>, which is an incomplete type until fully qualified. This doesn't change merely because

  • your code is inside the template class
  • you don't actually use the template parameter.
Pontus Gagge
I would expect code to be compilable even if I do not specify T parameter as getStrings() is not dependent on T.
Well, I like a compiler that doesn't try to guess what I mean. In most cases, if a template parameter doesn't get used, it would be because I'd missed something. (If you want maximum permissiveness, try old-school Perl rather than Java!)
Pontus Gagge
A: 

Java Generics are implemented using Type Erasure, which means that the compiler will emit code for each instantiation of a generic type (e.g. ForEachLoop<Something>) that basically uses the "cast to object" idiom. So your line ForEachLoop f = new ForEachLoop(); will generate a "straight" instantiation where the type argument defaults to object.

So what you really have is a ForEachLoop<object>, which is why you get the casting error.

Johannes Rudolph
He does not use generic type, In fact, ForEachLoop<Object> should also work.
Georgy Bolyuba
And where would that cast to T be?
musiKk
@Musikk: In fact the cast to T lives at every occasion "T is used as T" (e.g. in a method of return type T).
Johannes Rudolph
I want to cast to String which is explicitly set for method "public Collection<String> getStrings()" not to T.
@Johannes: Well yes, but I just mentioned that because in the question the type parameter T is never used.
musiKk
+1  A: 

The equivalent expression of you for loop is (according to the java language spec):

for (Iterator<String> i = f.getStrings().iterator(); i.hasNext(); ) {
   String a = i.next();
   System.out.println(a);
}

ForEachLoop is a generic class but f you used the raw type. So the compiler has to assume, that the iterator can return Object instances or instances of subtypes. And gives a warning, because you assign that raw iterator to a parametized iterator.

As long as your collection inside he ForEachLoop instance just contains Strings, you won't see a runtime error.

To get rid of the warning, parametize the instantiation part, like:

ForEachLoop<String> f = new ForEachLoop<String>();
Andreas_D
Any parametrization should work, even with <Object>
Georgy Bolyuba
@Georgy - sure, but the code in his question used ´<String>´ so I chose the same type in my answer.
Andreas_D