views:

268

answers:

5
public void wahey(List<Object> list) {}

wahey(new LinkedList<Number>());

The call to the method will not type-check. I can't even cast the parameter as follows:

wahey((List<Object>) new LinkedList<Number>());

From my research, I have gathered that the reason for not allowing this is type-safety. If we were allowed to do the above, then we could have the following:

List<Double> ld;
wahey(ld);

Inside the method wahey, we could add some Strings to the input list (as the parameter maintains a List<Object> reference). Now, after the method call, ld refers to a list with a type List<Double>, but the actual list contains some String objects!

This seems different to the normal way Java works without generics. For instance:

Object o;
Double d;
String s;

o = s;
d = (Double) o;

What we are doing here is essentially the same thing, except this will pass compile-time checks and only fail at run-time. The version with Lists won't compile.

This leads me to believe this is purely a design decision with regards to the type restrictions on generics. I was hoping to get some comments on this decision?

+6  A: 

Consider if it was...

List<Integer> nums = new ArrayList<Integer>();
List<Object> objs = nums
objs.add("Oh no!");
int x = nums.get(0); //throws ClassCastException

You would be able to add anything of the parent type to the list, which may not be what it was formerly declared as, which as the above example demonstrates, causes all sorts of problems. Thus, it is not allowed.

James
This is correct, but the question indicates that he already understands this, and wants to know why the decision was made to prohibit this behavior while still allowing other forms of type-unsafe behavior.
Tyler McHenry
I already understood this example. I tried to explain this scenario in the question (as Tyler said), but thank you for the comment.
Jack Griffith
this type unsafe behavior actually *is* allowed with arrays. The following compiles just fine but generates a runtime error: `Object[] arr = new Integer[2]; arr[0] = "oh no!";`
Kip
+6  A: 

What you are doing in the "without generics" example is a cast, which makes it clear that you are doing something type-unsafe. The equivalent with generics would be:

Object o;
List<Double> d;
String s;

o = s;
d.add((Double) o);

Which behaves the same way (compiles, but fails at runtime). The reason for not allowing the behavior you're asking about is because it would allow implicit type-unsafe actions, which are much harder to notice in code. For example:

public void Foo(List<Object> list, Object obj) {
  list.add(obj);
}

This looks perfectly fine and type-safe until you call it like this:

List<Double> list_d;
String s;

Foo(list_d, s);

Which also looks type-safe, because you as the caller don't necessarily know what Foo is going to do with its parameters.

So in that case you have two seemingly type-safe bits of code, which together end up being type-unsafe. That's bad, because it's hidden and therefore hard to avoid and harder to debug.

Tyler McHenry
Ah, I see. Thank you very much. :)
Jack Griffith
+4  A: 

They aren't subtypes of each other due how generics work. What you want is to declare your function like this:

public void wahey(List<?> list) {}

Then it will accept a List of anything that extends Object. You can also do:

public void wahey(List<? extends Number> list) {}

This will let you take in Lists of something that's a subclass of Number.

I'd recommend you pick up a copy of "Java Generics and Collections" by Maurice Naftalin & Philip Wadler.

Norman
+1, this is how generics works. **DO** pick up a copy of Java Generics and Collections. Awesome book!
WolfmanDragon
This wasn't my question. I know these are the rules of sub-typing with generics.
Jack Griffith
+2  A: 

There are essentially two dimensions of abstraction here, the list abstraction and the abstraction of its contents. It's perfectly fine to vary along the list abstraction - to say, for instance, that it's a LinkedList or an ArrayList - but it's not fine to further restrict the contents, to say: This (list which holds objects) is a (linked list which holds only numbers). Because any reference that knows it as a (list which holds objects) understands, by the contract of its type, that it can hold any object.

This is quite different from what you have done in the non-generics example code, where you've said: treat this String as if it were a Double. You are instead trying to say: treat this (list which holds only numbers) as a (list which holds anything). And it doesn't, and the compiler can detect it, so it doesn't let you get away with it.

Carl Manaster
Good explanation, thank you.
Jack Griffith
+1  A: 

"What we are doing here is essentially the same thing, except this will pass compile-time checks and only fail at run-time. The version with Lists won't compile."

What you're observing makes perfect sense when you consider that the main purpose of Java generics is to get type incompatibilities to fail at compile time instead of run time.

From java.sun.com

Generics provides a way for you to communicate the type of a collection to the compiler, so that it can be checked. Once the compiler knows the element type of the collection, the compiler can check that you have used the collection consistently and can insert the correct casts on values being taken out of the collection.

Graphics Noob