Why is it possible to get back an object of "incorrect-type" from the parametrized List in Java?
Asked Answered
I

5

21

Here's a code snippet:

import java.util.*;
class Test
{
    public static void main(String[] args)
    {
        List<Integer> list = new ArrayList<>();
        addToList(list);
        Integer i = list.get(0); //#1 fails at run-time
        String s = list.get(0); //#2 fails at compile-time
        list.get(0); //#3 works fine
        System.out.println(list.get(0)); //#4 works fine, prints "string"
    }
    static void addToList(List list){
        list.add("string");
    }
}

I understand why is it possible to insert an object of String class in parametrized List.

It seems like I understand why code marked with #1 and #2 fails.

But why do #3 and #4 work? As far as I understand, the compiler adds appropriate casts after type-erasure, so when I call list.get(0), this method should return an Object previously casted to Integer. So why there is no ClassCastException occures at #3 and #4 at run-time?

Insobriety answered 4/3, 2015 at 16:26 Comment(1)
Because not all your List uses were parametrized :)Endways
D
22

The #3 works because the object returned by get(int) is ignored. Whatever is stored at position 0 is returned, but since there is no cast, no error happens.

The #4 works fine for the same reason: the object produced by get(0) is treated like java.lang.Object subclass in println, because toString is called. Since toString() is available for all Java objects, the call completes without an error.

Darkling answered 4/3, 2015 at 16:31 Comment(3)
You mean that casting to Integer takes place only if i assign the result of get(0) to some variable?Insobriety
@Insobriety Yes. There is no forced casting to Integer unless you assign the value to something that needs a cast.Darkling
+1. The JLS doesn't actually specify that "casting", per se, takes place; instead, it specifies that assignment conversion (§5.2) and method invocation conversion (§5.3) can raise ClassCastExceptions if the instance's runtime-type doesn't match the target type. The OP's #3 and #4 don't trigger either of those rules, so there's no ClassCastException.Tetracaine
F
8

First the reason why you can add a string to a List<Integer>. In the method

static void addToList(List list){

you use a raw type. Raw types exist purely for compatibility with older Java versions and should not be used in new code. Within the addToList method the Java compiler does not know that list should only contain integers, and therefore it doesn't complain when a String is added to it.

As for the different behavior of you two statements. Integer i = list.get(0) does not fail at compile time, because Java thinks that list only contains Integers. Only at runtime it turns out that the first element of list is not an Integer, and therefore you get a ClassCastException.

String s = list.get(0) fails at compile time because the Java compiler assumes that list only contains Integers, and so it assumes you try to assign an Integer to a String reference.

Just list.get(0) does not store the result of the method call. So neither at compile time nor at run time there is any reason for a failure.

Finally, System.out.println(list.get(0)) work because System.out is a PrintStream and has a println(Object) method, which can be called with an Integer argument.

Father answered 4/3, 2015 at 16:33 Comment(0)
P
5

If you look at ArrayList#get method. It is this:

public E get(int index) {
   //body
}

But at runtime it is actually:

public Object get(int index) {
       //body
}

So when you do Integer i = list.get(0); The compiler converts it to:

Integer i = (Integer)list.get(0);

Now at runtime, list.get(0) returns an Object type (which is actually String). It now tries converting String => Integer and it fails.

3

Because it is just:

list.get(0)

The compiler does add typecasting to anything. So it is just, list.get(0).

4

System.out.println(list.get(0));

list.get(0) returns an Object type. So public void println(Object x) method of the PrintStream gets called.

Remember println(Object x) gets called and not println(String x).

Prognosticate answered 5/3, 2015 at 7:23 Comment(0)
A
2

4: The overload System.out.println(Object) is being called, since Integer <=_T Object (read: Integer is-a Object). Note that list.get(int) returns an Object in run-time since the type parameter gets erased. Now read

http://docs.oracle.com/javase/tutorial/java/generics/erasure.html

that tells you "Insert type casts if necessary to preserve type safety.", since a type cast is not necessary from Object to Object the ClassCastException cannot result.

For the same reason there is no type cast at 3, however side effects of the method call could happen of which List.get has none.

Assimilable answered 4/3, 2015 at 16:37 Comment(0)
C
1

The casts are applied to the return type of get, not to the add that's introducing the pollution. Otherwise, you'd be getting either a compile-time error or an exception at that point, since you can't cast a String to an Integer.

Cinchonidine answered 4/3, 2015 at 16:29 Comment(2)
Sorry, but you seem to have misunderstood the question.Tetracaine
(Feel free to fix your answer, by the way, and I'll retract my downvote. Or you can just delete it, if you feel that the other answers are sufficient.)Tetracaine

© 2022 - 2024 — McMap. All rights reserved.