7

I came across the following code, a simple example of adding elements to List

    List list = new ArrayList<Integer>();
    ListIterator<Integer> litr = null;
    list.add("A");

    list.add("1");

    list.add(5);

    litr = list.listIterator();
    while(litr.hasNext()){
        System.out.println("UIterating " + litr.next());
    }

I expected it to throw an ClassCastException, but rather it wrote this to the console

A
1
5

which looks weird. When i tried:

 List<Integer> list = new ArrayList<Integer>();

I got a compile time error.

I would be grateful if someone could explain how the String objects are added to the ArrayList

Santhosh
  • 8,045
  • 2
  • 26
  • 54
  • 4
    http://stackoverflow.com/questions/339699/java-generics-type-erasure-when-and-what-happens – CupawnTae May 18 '15 at 13:50
  • 4
    http://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it – Reimeus May 18 '15 at 13:51
  • 1
    I can sum it up in one sentence: "In Java, generics are only done at compile time." Having said that, Java throws a warning at you about using raw types for this reason. – Powerlord May 18 '15 at 14:04

2 Answers2

4

You assigned the new ArrayList to an untyped List. Generic type restrictions don't apply to an untyped List, it will let you put whatever you want in it. The compiler does not keep track that your untyped List refers to something that was declared with a generic type.

In any case this wouldn't produce a ClassCastException, generics only affect compilation. At runtime

The case where you put the type on the list variable:

List<Integer> list = new ArrayList<Integer>();

is preferred, it should generate a compiler error telling you you're putting the wrong type in the collection.

There's a description of how legacy, non-generic code and generic code interoperate in this article:

In proper generic code, Collection would always be accompanied by a type parameter. When a generic type like Collection is used without a type parameter, it's called a raw type.

Most people's first instinct is that Collection really means Collection<Object>. However, as we saw earlier, it isn't safe to pass a Collection<Part> in a place where a Collection<Object> is required. It's more accurate to say that the type Collection denotes a collection of some unknown type, just like Collection<?>.

But wait, that can't be right either! Consider the call to getParts(), which returns a Collection. This is then assigned to k, which is a Collection<Part>. If the result of the call is a Collection<?>, the assignment would be an error.

In reality, the assignment is legal, but it generates an unchecked warning. The warning is needed, because the fact is that the compiler can't guarantee its correctness. We have no way of checking the legacy code in getAssembly() to ensure that indeed the collection being returned is a collection of Parts. The type used in the code is Collection, and one could legally insert all kinds of objects into such a collection.

So, shouldn't this be an error? Theoretically speaking, yes; but practically speaking, if generic code is going to call legacy code, this has to be allowed. It's up to you, the programmer, to satisfy yourself that in this case, the assignment is safe because the contract of getAssembly() says it returns a collection of Parts, even though the type signature doesn't show this.

Community
  • 1
  • 1
Nathan Hughes
  • 85,411
  • 19
  • 161
  • 250
  • Just a FYI: You should put code blocks around the code in the quote. Otherwise, you can't see that one of the Collections is `Collection` – Powerlord May 18 '15 at 14:02
  • @NathanHughes Thanks for your answer . Got an idea about the untyped list and raw collection – Santhosh May 19 '15 at 06:29
1

This is possible because of how generics are implemented in Java - using type erasure, and because Java supports raw types for backward compatibility with old versions of Java (1.4 and older).

Generics only exist in your source code. The compiler uses them to check the types at compile-time, but then throws away the generics. At runtime, a List<Integer> is just a List of objects, and it doesn't know that it's a list that should contain only Integer objects.

Java supports the use of raw types such as List instead of List<Integer> for backward compatibility with old versions. When you use a raw type, as you are doing in your code above, you get a compiler warning. You should not use raw types in new code - only ever use them when you need to deal with old code that you can't change.

The combination of raw types and type erasure allows you to put types of objects in lists that you shouldn't be putting in there.

Because the List at runtime doesn't know anything about the type that its elements are supposed to have, it doesn't check anything so you will not get a ClassCastException.

Community
  • 1
  • 1
Jesper
  • 186,095
  • 42
  • 296
  • 332