Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: why is casting possible on wildcard collections?

Tags:

java

Suppose we have a class A, and a class B, which inherits from class A. Let's say we have:

Set<A> setOfAs = new HashSet<>();

The following casting:

((Set<B>) setOfAs)

will give us run-time error.

However, if we use wildcard and define the following set:

Set<? extends A> setOfAs = new HashSet<>();

we have no problem to do the casting:

((Set<B>) setOfAs)

Why casting a collection of a wildcard is allowed, while casting a collection of "regular" type is forbidden?

like image 520
CrazySynthax Avatar asked Jun 25 '20 09:06

CrazySynthax


2 Answers

we have no problem to do the casting:

You will have an unchecked cast warning, so it's not really the case that you have no problem; it's just that the compiler can't prove it's definitely wrong, and can't put anything into the bytecode to catch the fact it is wrong at runtime.

A Set<? extends T> is a Set where it can be assumed that all members can be safely cast to T without a ClassCastException.

A Set<? super T> would be a Set where it is known to be safe to add a T to it without causing a ClassCastException in places relying on the type of elements in the Set (I believe the correct technical term for this is without causing heap pollution).

A Set<T> is the intersection of these two bounded types: you can add instances of T to it, and all elements in it are instances of T.

By these definitions, a Set<B> can act as a Set<? extends A>, because anything that can be cast to an B can also be cast to A.

However, a Set<A> cannot act as a Set<B>, because it may contain instances of A which aren't instances of B.

like image 105
Andy Turner Avatar answered Oct 27 '22 21:10

Andy Turner


The whole idea of casting is saying "I know more than you, compiler!" when there's some uncertainty about the actual type of an object.

In the second case, this makes total sense. The compiler knows that setOfAs is of type Set<? extends A>, which means "a Set of an unknown type, and that unknown type extends A". There's uncertainty as to what type of HashSet it could be. As far as the the compiler is concerned, it could be HashSet<B>...

Set<? extends A> setOfAs = new HashSet<A>();

but it also could be HashSet<A>...

Set<? extends A> setOfAs = new HashSet<B>();

You, by means of casting, say "No, setOfAs is a HashSet<B>". The compiler goes, "Well, it could be that, so I'll trust you". Whether your extra knowledge is actually correct, is a separate matter.

In the first case however, setofAs is of type HashSet<A>. Since a variable of type HashSet<A> can never store an object of type HashSet<B>, i.e. this does not compile:

Set<A> setOfAs = new HashSet<B>();

There is no uncertainty about the generic parameter of Set. It's gotta be A. You, trying to cast to HashSet<B>, will only result in the compiler saying "No, it can never be that!"

like image 38
Sweeper Avatar answered Oct 27 '22 19:10

Sweeper