2

Can someone explain this behaviour to me:

Note: That T is never used in SomeThingGeneric

   public static class SomeThingGeneric<T> {
        public List<String> getSomeList() {
            return null;
        }
    }

final SomeThingGeneric<Object> someThingGenericObject = new SomeThingGeneric<Object>();
final SomeThingGeneric<?> someThingGenericWildcard    = new SomeThingGeneric<Object>();
final SomeThingGeneric someThingGenericRaw            = new SomeThingGeneric<Object>();

for (final String s : someThingGenericObject.getSomeList()) { }   // 1 - compiles
for (final String s : someThingGenericWildcard.getSomeList()) { } // 2 - compiles
for (final String s : someThingGenericRaw.getSomeList()) { }      // 3 - does not compile!

(1) and (2) compiles but (3) fails with following message:

incompatible types
found   : java.lang.Object
required: java.lang.String

If anyone wants the full code, here it is. I have verified this in both Java 5 and 6.

pathikrit
  • 32,469
  • 37
  • 142
  • 221
  • 2
    possible duplicate of [Why does this class behave differently when I don't supply a generic type?](http://stackoverflow.com/questions/15735235/why-does-this-class-behave-differently-when-i-dont-supply-a-generic-type) – Louis Wasserman Apr 09 '13 at 14:47
  • Nevertheless, the question and the answer both apply here. (In particular, the answer is "it was done this way for backwards compatibility, and it shouldn't be an issue because you should never use raw types in new code.") – Louis Wasserman Apr 09 '13 at 18:21
  • @wrick `Looking for an answer drawing from credible and/or official sources.` - what else, besides the citations from the JLS, would you like to see? – Andreas Fester Apr 12 '13 at 07:19

4 Answers4

4

Well, this is an interesting question despite the downvotes. I believe the answer to the question lies in this portion of the JLS:

The type of a constructor (§8.8), instance method (§8.4, §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 raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

Effectively, your method public List<String> getSomeList gets an effective signature of public List getSomeList, in the scenario where you accessing it via a raw type. And as such, the list iterator for the resulting list then 'returns' objects instead of strings.

Perception
  • 79,279
  • 19
  • 185
  • 195
  • 1
    There is no particularly good reason that I can think of or have found in any text. If the fields/methods were typed to the generic parameter of the class that would make (obvious) sense. Could be to enable some kind of compatibility with legacy code. Nevertheless, the behavior is documented in the JLS, fwiw. – Perception Apr 09 '13 at 07:59
3

The code can be broken down to the following SSCCE:

public static class SomeThingGeneric<T> {
    public List<String> getSomeList() {
        return null;
    }
}

public static void main(final String[] args) {
    final SomeThingGeneric someThingGenericRaw = new SomeThingGeneric<Object>();
    for (final String s : someThingGenericRaw.getSomeList()) { }        //  SAD compiler.  WTF?
}

This results in the compiler error Type mismatch: cannot convert from element type Object to String.

The question is: why does this error occur? T is not used anywhere in SomeThingGeneric, and especially not in the signature of the getSomeList() method. So, when we call getSomeList(), we should get a List<String>. However, we obviously get a raw List type.


The reason is type erasure. From the JLS:

Type erasure also maps the signature (§8.4.2) of a constructor or method to a signature that has no parameterized types or type variables.

And, the definition of a raw type says:

To facilitate interfacing with non-generic legacy code, it is possible to use as a type the erasure (§4.6) of a parameterized type (§4.5) or the erasure of an array type (§10.1) whose element type is a parameterized type. Such a type is called a raw type.

So, when using a raw type, it is the generic type with type erasure applied. When applying type erasure, all method signatures are mapped to types with no type parameters or type variables. Hence, the method List<String> getSomeList() from the example becomes List getSomeList() which results in the shown compiler error.

Andreas Fester
  • 36,091
  • 7
  • 95
  • 123
  • But note that I do not use T anywhere in SomeThingGeneric – pathikrit Apr 09 '13 at 07:31
  • 1
    Ah, good point - did not recognize this at first ... lets see ... (by the way, that is the reason why it is useful to describe the issue a littlebit in the question ;) ) – Andreas Fester Apr 09 '13 at 07:32
  • 1
    I did not downvote ;) Did you check with an earlier compiler version (1.6 / 1.5)? – Andreas Fester Apr 09 '13 at 07:42
  • Fails in both 1.6 and 1.5. Did not check 1.7+ – pathikrit Apr 09 '13 at 07:43
  • Also fails with 1.7 (Eclipse). I suppose that this is somehow related to type erasure, but did not find a reference yet ... – Andreas Fester Apr 09 '13 at 07:47
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/27825/discussion-between-andreas-and-wrick) – Andreas Fester Apr 09 '13 at 07:48
  • Glad to see you incorporated my JLS quote into your answer. But you might want to remove the first quote as its not directly related to the actual problem (and comes from section 4.6, not 4.8). – Perception Apr 09 '13 at 08:31
  • I agree that the link was wrong (thanks - fixed that), but does the part you quoted not describe the erasure **corresponding to the generic parameter itself** (which would be `T` in the example above)? IMHO, the fact that also non-related generic type parameters (like from the sample above) are also erased, is described more precisely in §4.6 – Andreas Fester Apr 09 '13 at 08:50
3

Answer: When you instantiate a generic type as a raw type, ALL of it's type instantiations are removed, leaving raw types everywhere (even if they don't relate to Type parameters, such as T in your Q). i.e. raw types are 100% "generics-free".

Reason: backwards compatability with pre-generic code.

Glen Best
  • 22,769
  • 3
  • 58
  • 74
0

For my opinion is a bug. Because it must work. The signature of the method do not use the generic T type. So it must compile.

Mark the entire class as erasure, do not compiling the very well defined generic methods, is not good.

Please report it.

Daniel De León
  • 13,196
  • 5
  • 87
  • 72
  • Its not a bug. It is well documented in the JLS. – Andreas Fester Apr 12 '13 at 07:39
  • The signature of the method is very well defined, so still I do not see the logic behind it to be rejected. – Daniel De León Apr 12 '13 at 07:59
  • The logic is: do not use raw types. They are provided for compatibility reasons only. See the link from @Louis in his comment. – Andreas Fester Apr 12 '13 at 08:04
  • Right, but do not compile any NOT erasure methods of a erasure class, it must not be acceptable, because the line of code consume a very well generic defined method and is still compatible, because the raw behavior is always there. – Daniel De León Apr 12 '13 at 08:19
  • 1
    When you design a language, you should keep this in mind: http://en.wikipedia.org/wiki/Principle_of_least_astonishment – pathikrit Apr 12 '13 at 11:14