4

Could someone please explain to me why this piece of code does not compile?

Even though it uses a generic class without providing the specific type T, it should be able to recognize at compile time that the ArrayList holds strings.

public class Test {
    public static void main(String[] args){
        Container container = new Container();
        container.strings.add("test");
        String s1 = container.strings.get(0); // does not compile
        ArrayList<String> local = container.strings;
        String s2 = local.get(0); // does compile
    }

    static class Container <T>{
        ArrayList<String> strings = new ArrayList<String>();
    }
}
ahbutfore
  • 399
  • 4
  • 10
  • 5
    You don't think the compilation error may be helpful in figuring out why it does not compile? – K-ballo Jan 01 '13 at 23:30

2 Answers2

7

When you use a generic class as a raw type (one where you don't specify a type), ALL generic information is stripped from the class (whether the omitted type is used or not).

So when you code Container container (instead of Container<SomeClass> container) ArrayList<String> strings becomes ArrayList strings, which is used as if it were ArrayList<Object>.

To "fix", specify the type for Container (even though you don't use the type):

Container<Object> container = new Container<Object>();

the rest will now compile.


The reason this is done is to be backward compatible with earlier pre-generic versions of java (1.4 and earlier)

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • That sounds a bit extreme... any idea why they decided to make it that way? – K-ballo Jan 01 '13 at 23:34
  • 2
    @K-ballo: Because Java generics were glued on as an afterthought and at runtime everything is `Object`. Generics only really do their thing at compile time. – Matti Virkkunen Jan 01 '13 at 23:35
  • 1
    @Bohemian: I still don't see why that calls for stripping a concrete type that is not related to the generics parameter, but hey... afterthought :P – K-ballo Jan 01 '13 at 23:41
  • @K-ballo it is severe, and it can lead to some hard to find bugs, but that's java for you - strict – Bohemian Jan 01 '13 at 23:47
  • *Backword compatibility* means that legacy code is still compilable and doesn't break at runtime, but I can't find a way how not erasing types in a raw type would bring to failure – Raffaele Jan 02 '13 at 10:52
  • @Raffaele It's the other way around... it's so things don't break when legacy code calls modern (generic) code. – Bohemian Jan 02 '13 at 12:19
  • @Bohemian actually it is the *only* way things would break, because newer code can't use *raw types* from legacy libraries written before generics came out. I can't understand the point of this spec, but since you got it, would you make an example? – Raffaele Jan 02 '13 at 12:34
  • 1
    @Raffaele Google gave me these SO links: [this brief one](http://stackoverflow.com/questions/13501444) and [this authoritative one](http://stackoverflow.com/questions/2770321) – Bohemian Jan 02 '13 at 12:57
  • I appreciate your research, but still consider this decision sort of a *glitch*. Erasing only types parameterized using the *T* parameter would have been a wiser choice to me, it would have provided reasonable interop and more expected results, because the kind of avoided breakages is still not avoidable when the new version of a library uses generics without parameterizing its own types – Raffaele Jan 02 '13 at 13:43
  • 1
    @Raffaele I definitely understand your point of view and probably agree. – Bohemian Jan 03 '13 at 19:33
  • ;) what hat are you running for? – Raffaele Jan 03 '13 at 19:43
0

As Bohemian said, every type argument in a raw type is thrown away. At the beginning I thought it was a bug, but there's even an entry in the bug database (#6244346) which explicitely quotes the relevant JLS §4.8

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.

The type of a static method or static field 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 type arguments to a non-static type member of a raw type that is not inherited from its superclasses or superinterfaces.

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

The reason why you can't get a String from a raw List, but you can assign a List to a List<String> is because in the latter the compiler issues a warning (unchecked conversion) but you don't report it (do you read warnings, don't you? :P). I tested your code with javac and the Eclipse compiler, and both honor the spec.

Raw types were introduced for interoperability with legacy code, but in this case I can't figure out a way how keeping type information for non-static members would have break things. Using a parameterized type instead of a raw type would mean code partially ported, so maybe in this case the purpose is not simply backcompat, but ensuring a coherent codebase, either the code is full 1.4, or it's entirely Java 5+. Another option is that using raw types like this may have slowed down the adoption of unbounded wildcards in similar contexts.

BTW (but I think you figured it out by yourself) you can simply use an unbounded wildcard if you don't use the type parameter, ie Container<?>

Raffaele
  • 20,627
  • 6
  • 47
  • 86