0

Does anyone know why is the following forbidden:

Collection<SomeType> collection;
Collection<OtherType> otherCollection = (Collection<OtherType>) collection;

while the following is allowed:

Collection<SomeType> collection;
Collection<OtherType> otherCollection = (Collection<OtherType>) (Collection<?>) collection;

I already used the second construction in one of my program. It solved the issue I had, but doing two casts in a row looked odd to me.

Étienne Miret
  • 6,448
  • 5
  • 24
  • 36
  • I cannot see good practice within this method, what happens if OtherType, in a future, differs so much from SomeType? application will stop working. – RamonBoza Oct 03 '13 at 07:13
  • What will happen is exactly the same as if you cast SomeType to OtherType. Casts are allowed in java, you know. – Étienne Miret Oct 03 '13 at 07:18
  • But that does not means that casting is a good practice if not doing between same hierarchical elements – RamonBoza Oct 03 '13 at 07:23

4 Answers4

2

It is forbidden because it would defeat the purpose of generics, that is being sure that a collection actually contains what its type states.

A few examples.

Collection<String> strings = Arrays.asList("A", "B", "C");
// If this was allowed...
Collection<Integer> integers = (Collection<Integer>) strings;
// What would you expect from this?
for (Integer i : integers) ...

A more subtle situation is when, in your terms, OtherType is a supertype of SomeType.

Collection<Integer> integers = Arrays.asList(1, 2, 3);
// Shouldn't this be legal? Integer extends Number after all?
Collection<Numbers> numbers = integers;
// I might do this then
numbers.add(2.0);
// Ouch! integers now contains a Double.
for (Integer i : integers) ...

Depending on the situation, you can use the extends keyword to your advantage.

Collection<Integer> integers = Arrays.asList(1, 2, 3);
// This IS legal
Collection<? extends Numbers> numbers = integers;
// This however does not compile: you cannot add anything to the Collection
numbers.add(2.0);

More info on extends and super keywords: What is PECS (Producer Extends Consumer Super)?

Community
  • 1
  • 1
Flavio
  • 11,925
  • 3
  • 32
  • 36
1

The first one is against the compiler semantics and so it will not compile. But when compiled, generics is removed, so actually you are able to store whatever you want into that collection.

And since you trick the compiler rules by your second code snippet (by casting to collection without generics) it will compile.

Manuel Manhart
  • 4,819
  • 3
  • 24
  • 28
  • Why is the first one against compiler semantics? – Étienne Miret Oct 03 '13 at 07:17
  • 2
    @EtienneMiret Because you're telling the compiler that Apples are Oranges, but the compiler knows you're lying. You should fix your generics instead of doing hacks like this. Give us a real world example where you need to do a conversion like this, and we'll show you how to fix it properly. – Kayaman Oct 03 '13 at 07:25
  • I totally agree with Kayaman. If I have a mixed collection, I dont use generics, and if I have a common superclass, I can use that in generics. So I also would see no use of casting like your example. – Manuel Manhart Oct 03 '13 at 07:29
  • 1
    @ManuelM. You should *always* use generics - raw types only exist for backwards compatibility with pre-1.5 source code. There's always a common superclass, even in the most heterogeneous case you can use `Collection`. This (to me) is much clearer that it's a collection of mixed things than the raw type `Collection`. – Andrzej Doyle Oct 03 '13 at 08:49
1

Assuming the below statements work :

Collection<SomeType> collection;
Collection<OtherType> otherCollection = (Collection<OtherType>) collection; //illegal

This would break the type safety generics were supposed to provide. Imagine you could assign a Collection<SomeType> to a Collection<OtherType>. Then the following code would allow you to put something that wasn't a SomeType into a Collection<SomeType>:

otherCollection.add(new OtherType());

Because otherCollection is a Collection<OtherType>, adding a new OtherType() to it seems perfectly legal. But otherCollection is actually referring to a collection of type SomeType.

And this is allowed:

Collection<SomeType> collection;
Collection<OtherType> otherCollection = (Collection<OtherType>) (Collection<?>) collection;

This is an implication of type erasure. As generics are implemented almost entirely in the Java compiler, and not in the runtime, nearly all type information about generic types has been erased by the time the bytecode is generated. So at runtime, Collection<SomeType> and Collection<OtherType> are the same class and both of them are subtypes of Collection<?>. With this conversion, the compiler will simply emit an unchecked warning, because it doesn't know if the cast is safe or not.

But a casting, like the one you have given in the second example, should be avoided to avoid ClassCastException at runtime. Consider the below series of statements:

Collection<SomeType> collection;
Collection<OtherType> otherCollection = (Collection<OtherType>) (Collection<?>) collection;
collection.add(new SomeType());
otherCollection.add(new OtherType());

Now after the 4th statement, there is no way for the original collection to tell what specific type of objects it contains; which might lead to ClassCastException at runtime while accessing elements from the collection.

Debojit Saikia
  • 10,532
  • 3
  • 35
  • 46
  • 2
    The unchecked warning is very relevant here. In the first example, the compiler can say for sure that "this can never work". In the second example, you've "tricked" it by obscuring the generic type information - so all it can say is "I have no idea whether this is legitimate, and it may fail at runtime". – Andrzej Doyle Oct 03 '13 at 08:45
  • 1
    Yes @AndrzejDoyle. I think a casting like the second example should not be done, as it will break the very idea of `typed collection`. – Debojit Saikia Oct 03 '13 at 09:46
0

According to SCJP 6 Exam Book.-

At runtime the JVM DOES NOT KNOWS the type of a collection. All the generic type information is removed during compilation, so by the time it gets to the JVM, there is simply no way to recognize the disaster of putting a SomeType into a Collection<OtherType>.

If you try to cast Collection<SomeType> to Collection<OtherType>, the compiler will stop you, since at runtime the JVM would have no way to stop you from adding a OtherType to what was created as a SomeType collection.

Anyway, assuming there's an heritage relationship between SomeType and OtherType, I'm guessing you'd like to do something like this.-

Collection<SomeType> collection = null;
Collection<? extends OtherType> otherCollection = collection;
ssantos
  • 16,001
  • 7
  • 50
  • 70