2

I have this scenario, but why java is not respecting the class hierarchy?

import ... public class GenericsTest { public static void main(String[] args) { List<Integer> myList = new ArrayList<Integer>(); myList.add(1); myList.add(new Integer(2)); new GenericsTest().doInsert(myList); } private void doInsert(List<? extends Number> myList) { // Number <- Integer myList.add(new Integer(1)); // This don't compiles } } 

Can someone explains this? Thanks!!!

2
  • Suppose it's an imaginary number. How would you insert an Integer into a list of ImaginaryNumbers? Commented Feb 27, 2013 at 20:02
  • doInsert(new ArrayList<Short>()), for example. Commented Feb 27, 2013 at 20:02

4 Answers 4

2

The wildcard in generics does not mean "anything" it means "I don't know." So List<? extends Number> is not "a list that can hold anything that extends number." It is "A list of something that extends number, but I don't know what they are." So it's illegal to add an Integer to it, because you don't know if Integer is the thing that extends number that this is a list of. A list of anything that extends Number is just List<Number>.

Sign up to request clarification or add additional context in comments.

2 Comments

Don't know what you mean. This works just fine: List<Number> list = new ArrayList<Number>(); list.add(Integer.valueOf(1));
Oh I see what you're saying. The thing is, remember in Java jars can be combined at runtime and execute code that they could not see at the time they were compiled. So even though a simple analysis of your code shows that the List is always a List<Integer> in this program, the compiler can't just analyze your code and assume that. It has to create code that is valid for all possible uses of the doInsert(List<? extends Number> myList) method signature.
1

Imagine what would happen with the following code, if the code you provided was legal:

List<Double> listDouble = new ArrayList<Double>(); doInsert(listDouble); Double d = listDouble.get(0); //ClassCastException! 

The compiler adds an implicit cast at the last line. This cast fails at runtime, and this breaks the type safety guarantee of generics, because the compiler didn't save you from ruining the list contents and your data type assumptions. It was supposed to hold only Double values, but an integer found a way inside.

Your options:

doInsert(List<? extends Number> myList) 

Allows iterating over Numbers, but not adding to the collection. Accepts list defined on any subtype of Number.

doInsert(List<Number> myList) 

Allows iterating and modifying, but only accepts lists defined exactly as List<Number>.

doInsert(List<? super Number> myList) 

Allows iterating over the items (as Object), and allows adding any kind of Number. Accepts only lists of Number of its ancestors (what makes it practically useless in this case).

6 Comments

Your first example would work if you did Number n=ListDouble.get(0); You just need to be careful how you reference.
@JohnKane: The whole idea of Java generics is type safety, guaranteed at compile time. There's no reason for a programmer to expect an Integer inside a List<Double>.
Yes, that is the point I was trying to make. The programmer wouldn't expect Integers, inside a List<Double> unless they had a very good reason to do so. You just have to reference the Object by the correct type. There are many times when this is used.
A simple example is class Object itself. There are methods there that are very useful which any class in Java inherits. Also, there are times when you need to call methods of that class regardless of the type of Object you are working with.
@JohnKane: polymorphism is not the issue here. Any class is also an Object, so it inherits all of Object's methods. What you describe smells like an abuse of generics, and a bad practice. If you can't compromise to type homogeneity in a collection, simply don't use generics.
|
0

The reason this will not work is because the type of the referenced List does not have to be a integer. However, the following will compile.

private void doInsert(List<Number> myList) { myList.add(new Integer(1)); } 

Along with:

private void doInsert(List<? extends Number> myList) { ((List<Number>)myList).add(new Integer(1)); } 

Ive appended this answer based on the comment from @Eyal Schneider to show that the second way listed does work if you set it up correctly. Though it is most likely not a good thing to do.

public class Test{ public void doInsert(List<? extends Number> myList) { ((List<Number>)myList).add(new Integer(1)); ((List<Number>)myList).add(new Short("2")); ((List<Number>)myList).add(new BigDecimal("3")); ... } public static void main(String[] args){ Test t=new Test(); List<Number> list=new ArrayList<Number>(); t.doInsert(list); for(Number number:list) System.out.println(number); } } 

2 Comments

Note that the latter compiles, but its not type safe. You may ruin a List<Long> passed as a parameter for example. That's why the compiler issues a type safety warning for this implementation.
Yes, however, if you are careful it should work. Ill add an example.
0

Consider the following counter example, assuming it was acceptable:

public class GenericsTest { public static void main(String[] args) { List<Integer> myList = new ArrayList<Integer>(); myList.add(1); myList.add(new Integer(2)); List<Integer> myDoubleList = new ArrayList<Double>(); myList.add(1.0); myList.add(new Double(2)); new GenericsTest().doInsert(myList); // Here you're trying to add an Integer into a List<Double> } private void doInsert(List<? extends Number> myList) { // Number <- Integer myList.add(new Integer(1)); // This don't compiles } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.