mdsimmo
mdsimmo

Reputation: 619

Casting generic type to a subtype

I have an ArrayList<Number> which I read out of a file. But I need it to be an ArrayList<Integer>. I know all the Numbers are Integers, so I thought I could do something like this:

ArrayList<Number> numbers = new ArrayList<>();
nubers.add( 1 );

// cast the list<Number> to a list<Integer>
ArrayList<Integer> ints = (ArrayList<Integer>)numbers;
ints.add( 2 );

However, this causes an compile error. But if I first cast numbers to an Object, the code runs perfectly (except of-course for the unsafe cast warning).

ArrayList<Integer> ints = (ArrayList<Integer>)(Object)numbers;

So why does the compiler complain at my cast when it's a valid cast?

Note: I'm not asking for a solution but for an explanation of why the cast does not compile.

Bonus question:

Thanks for your explanations, but they make me wonder why this line can compile in some environments? A brief summary of the line:

A Collection<Bundleable> is being cast to Collection<? extends Item> where Item implements Bundleable.

Upvotes: 2

Views: 520

Answers (3)

Jean-Baptiste Yun&#232;s
Jean-Baptiste Yun&#232;s

Reputation: 36391

Remember that generics are not real types, there is type erasure for them. So the compiler tries to verify many things about the way you use generics before compiling to bytecode. As ArrayList<Integer> is not as subtype of ArrayList<Number> (even if: Integer is subtype of Number or your list of Numbers is full of Integers), the compiler complains about the direct cast as it may be too dangerous (not really related types). In the second case, you pass over the compiler type-checking in some way, because of course every of your types is subtype of Object and the compiler can't enforce a rule, but he can warns you about the downcast...

Upvotes: 0

Tim Biegeleisen
Tim Biegeleisen

Reputation: 521028

Consider the following declaration:

ArrayList<Number> numbers;

the ArrayList numbers could later be instantiated with any generic which extends the Number class, such as:

numbers = new ArrayList<Float>();

Now, you can see the problem with the following cast:

ArrayList<Integer> ints = (ArrayList<Integer>)numbers;

But numbers contains Floats, not Integers. By the way, making the cast to Object does compile, but does so with a warning:

ArrayList<Integer> ints = (ArrayList<Integer>)(Object)numbers;

Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

The compiler is complaining about an unchecked cast, because it correctly deduces that the generic types may not match.

Upvotes: 1

Rohit Jain
Rohit Jain

Reputation: 213223

The type information of list is not available at runtime, i.e, generics are not reifiable. So at runtime, the cast will anyways work, as it's just like this:

ArrayList ints = (ArrayList)numbers;

No worries. But if allowed, the cast will result in issues down the line, like in below modified code of yours:

ArrayList<Number> numbers = new ArrayList<>();
nubers.add( 1.0 );

// cast the list<Number> to a list<Integer>
ArrayList<Integer> ints = (ArrayList<Integer>)numbers;
Integer x = ints.get(0);  // trouble here

Basically you added a double type to a List<Number> which is perfectly fine. If compiler allowed the casting, then it will also pass at runtime.

Going further, the last line (commented one) will fail with ClassCastException, because you're just trying to assign a Double type to Integer type. That's the reason compiler doesn't allow to do such casts. And you should not ignore unchecked cast warnings by compiler in case of generics (even though that is warning).

Upvotes: 4

Related Questions