I know Generics are invariant: for any two distinct types Type1 and Type2,
List< Type1> is neither a subtype nor a supertype of List<
Type2>
so
List<someObject> nums = new ArrayList<someObject>(); // this is correct
List<Object> nums = new ArrayList<subObject>(); // this is not correct
but
List<Number> nums = new ArrayList<Number>();
List<? super Number> sink = nums; ????(line 2)
let say if wildcard is Object so line 2 wil be
List<Object> sink = List<Number> nums
It seems that the invariant rule not applied here
can anyone explain to me why line 2 is compiled without error ?
Thank so much
If I am not wrong, you wish to have an explanation on how is the below valid code:
List<Number> nums = new ArrayList<Number>();
List<? super Number> sink = nums;
Invariance is property of the class on how its type parameter affects its subtyping.
Generics are invariant, but wild cards exist to help us with with sub-typing. They are not very useful as they dont representing anytype but represent a good hack. Below is valid
List<Animal> <: List<?>
List<?> <: List
Better example:
List<? super Animal> d1= new ArrayList<Animal>();
d1.add(new Animal());
d1.add(new Dog());
The above works, because d1 is of type List<? super Animal>. You can imagine add function to behave like:
boolean add(? super Animal e);
So to add method you can pass any variable that is subtype of ? super Animal. And
Animal <: ? super Animal
Dog <: ? super Animal
So adding a Dog or Animal works. So d1 acts as a list that can take any parameter of type Animal or subtype if it.
Similarly, you can also have below. But technically you cant add anything to this list. If there existed sometype in java that is subtype of every type, then you could properly add element of that type to it. Nothing Else.
ArrayList<? extends Animal> d1 = new ArrayList<Animal>();
Look at this answer for more info.
The invariant rule does indeed apply. The statement:
List<? super Number> sink = nums;
just doesn't work they way you are thinking.
I expect that you think that you can assign an object of type ArrayList to a variable of type List<? super Number> because the former is a subclass of the latter. This would break the invariant rule, but this is not what is happening.
The List<? super Number> variable represents the set of all possible List where someClass is Number or is an ancestor of Number.
let say if wildcard is Object so line 2 wil be
`List<Object> sink = List<Number> nums `
In this context the ? doesn't get set arbitrarily, so this doesn't happen.
Lets have:
List<Integer> li = new ArrayList<>();
List<Object> lo = new ArrayList<>();
l = lo;//not allowed
lo.add("string");
Integer i = li.get(0);//that is string there
That is why you cannot make such assignment. I think compiler doesn't treat direct assignment of new ArrayList<Integer>() other then existing variable li.
Related
The whole idea of Generics is type-safety and preventing casts.
When i add() to a list that is <? super A>, the only way it lets me compile the get is to declare the variable type as Object.
ArrayList<? super A> list = new ArrayList<A>();
Object obj = list.get(0);
The fact is that I can only add A and A's sub-classes to this list.
So one, why doesnt it let me declare obj as type A?
A obj = list.get(0);
And two, the fact that type is now Object, it seems to loosen up the whole Generics idea - i am containing it in a umbrella type, and i need to cast the .get()
You define the list as a list of <? super A>, this means the list contains elements which are A, or any of it's superclasses, this causes a problem, because there can be any number of superclasses, which could go all the way up to object, so all the compiler can guarantee is that the item returned from the list is an object.
If you want to have a list that contains As you should define it as a List<A> which guarentees the list will contain only As or A's subclasses
If you have a variable of type List<? super A>, you can only add values of type A to it through that variable. That doesn't mean it can't have other objects in it:
List<Object> l = new ArrayList<>();
l.add(new Object());
List<? super A> l2 = l;
l2.add can only be called with values of type A, but l2.get could return non-A instances.
Rather than attempt this in words, I'll just give an example:
I have an Animal class, as well as a Dog,Fish,Cat, etc. which extend Animal.
I have three different methods, which return Map<String,List<Dog>>, Map<String,List<Fish>>, Map<String,List<Cat>>. We'll call these getDogMap, getCatMap, and getFishMap.
I am writing a generic method which, depending on various parameters, calls one of these methods. Here is what I expected to be allowed to do:
public void <A extends Animal> doSomething(){
Map<String,List<A>> someMap;
if(someCondition){
someMap = getDogMap();
}else if(anotherCondition){
someMap = getFishMap();
}else{
someMap = getCatMap():
}
}
Or at least, that with casting ala someMap = (Map<String,List<Dog>>) getDogMap();
However, this does not work. Eclipse tells me "Type mismatch: cannot convert from Map<String,List<Dog>> to Map<String,List<A>>" If I try to force the cast, it tells me "Cannot cast from Map<STring,List<Dog>> to Map<String,List<A>>".
What am I doing wrong?
public void <A extends Animal> doesn't mean "A is any type that extends Animal", it means "A is a specific one of the types that extends Animal". You need to use the following declarations:
public void doSomething() {
Map<String, ? extends List<? extends Animal>> someMap;
// ...
}
The construct ? extends Animal is how you express "any type that extends Animal".
The reason you have to use that declaration is that, counter-intuitively, the way subtype relationships between generic types work isn't exactly consistent with how they work between regular types. For instance, List<Dog> is a subtype of Collection<Dog>. It is not a subtype of List<Animal>, or Collection<Animal> etc. The reason why this isn't allowed is called heap pollution, also explained in Angelika Langer's FAQ. List<Dog> is, however, a subtype of List<? extends Animal>. A variable of type List<? extends Animal> may have assigned a List<Dog>, or a List<Cat>, or a List<Animal>. The important part is that the compiler doesn't know which of these it is, just that it is one of them.
Just like List<Dog> is not a subtype of List<Animal>, analogously it holds that Map<String, List<Dog>> is not a subtype of Map<String, List<? extends Animal>>.
The best way to demonstrate why generics work this way is proof by contradiction; that is, showing examples of (broken) code that would lead to errors were generics to work "intuitively". So, if List<Dog> were a subtype of List<Animal>, the following code would be valid:
List<Dog> dogs = new ArrayList<Dog>();
List<Animal> animals = dogs; // unsafe cast
// this operation violates type safety
animals.add(new Cat());
// would assign a Cat to a variable of type Dog without a compile error!
Dog dog = animals.get(0);
Similarly, for your Map:
Map<String, List<Dog>> dogses = new HashMap<String, List<Dog>>();
Map<String, List<? extends Animal>> animalses = dogses; // unsafe cast
List<Cat> cats = new ArrayList();
cats.put(new Cat());
animalses.put("cats", cats);
List<Dog> dogs = dogses.get("cats");
Dog dog = dogs.get(0); // uh-oh
The problem here lies in the way generic types work.
In Java, you would expect that Number is a superclass of Integer (and you would be right). You would also expect that Number[] is a superclass of Integer[]. You would also be right. However, you cannot say that List<Number> is a superclass of List<Integer>. This violates type safety.
List<Integer> intList = new ArrayList<Integer>();
List<Number> numList = intList; // the system stops you here
numList.add(Math.PI); // so that you don't do this
In you case, you have a type Animal and its subtypes Dog, Cat, and Fish. Extend this logic to your animals, and you see why you are having trouble casting List<Dog> to List<A>.
Now, you can use the addAll(Collection) method defined in the List interface.
List<Animal> animals = new List<Animal>();
List<Dog> dogs = new List<Dog>();
animals.addAll(dogs);
Please give me a hint as to what is going on here:
List<? extends Number> a = new ArrayList<Number>();
List<? extends Number> b = new ArrayList<Number>();
a.addAll(b); // ouch! compiler yells at me, see the block below:
/*
incompatible types
found : java.util.List<capture#714 of ? extends java.lang.Number>
required: java.util.List<java.lang.Number>
*/
This simple code does not compile. I vaguely remember something related to type captures, like those should be mostly used in interface specs, not the actual code, but I never got dumbfounded like that.
This of course might be fixed brute-forcefully, like that:
List<? extends Number> a = new ArrayList<Number>();
List<? extends Number> b = new ArrayList<Number>();
#SuppressWarnings({"unchecked"})
List<Number> aPlain = (List<Number>) a;
#SuppressWarnings({"unchecked"})
List<Number> bPlain = (List<Number>) b;
aPlain.addAll(bPlain);
So, do I really have to either give up captures in the declaration (the capture came to me from an interface, so I'll have to change some API), or stick with type casts with suppression annotations (which generally suck and complicates code a bit)?
You have essentially two lists of possibly different types. Because ? extends Number means a class which extends Number. So for list a it can be classA and for list b it can be for example classB. They are not compatible, they can be totally different.
The problem is that if you use List<? extends Number> you could actually do:
List<? extends Number> a = new ArrayList<Integer>();
List<? extends Number> b = new ArrayList<Double>();
a.addAll(b); //ouch, would add Doubles to an Integer list
The compiler can't tell from List<? extends Number> what the actual type parameter is and thus won't let you do the add operation.
You also shouldn't cast the lists to List<Number> if you get them as a parameter, since you could actually have a list of Integer objects and add Double objects to it.
In that case you better create a new List<Number> and add the objects from both lists:
List<Number> c = new ArrayList<Number>(a.size() + b.size());
c.addAll(a);
c.addAll(b);
Edit: in case you create both lists locally, you would not neet the ? wildcard anyway (since you'd always have List<Number>).
PECS (producer-extends, consumer-super)
You cannot put anything into a type declared with an EXTENDS wildcard
except for the value null, which belongs to every reference type
You cannot get anything out from a type declared with an SUPER
wildcard except for a value of type Object, which is a super type
of every reference type
Don't use the ? wildcard. It means "Some specific type I don't know", and since you don't know the type, you can't add anything to the list. Change your code to use List<Number> and everything will work.
This is fast becoming the most frequently asked Java question, in a hundred variations...
The thing is:
List<? extends Number> a = new ArrayList<Number>();
List<? extends Number> b = new ArrayList<Number>();
could also be read as:
List<x extends Number> a = new ArrayList<Number>();
List<y extends Number> b = new ArrayList<Number>();
How should the compiler know that x and y are the same?
Having the following simple class structure:
class A {
}
class B extends A {
}
class C extends B {
}
I'm creating an ArrayList to keep objects of the earlier created classes:
List<? extends A> list1 = new ArrayList<A>();
List<? extends B> list2 = new ArrayList<B>();
List<? extends C> list3 = new ArrayList<C>();
List<? super A> list4 = new ArrayList<A>();
List<? super B> list5 = new ArrayList<B>();
List<? super C> list6 = new ArrayList<C>();
To each of those lists I'm trying to add 1 object of each earlier created class: A,B,C. The only possible combination is:
adding object of class A,B,C to list4
adding object of class B and C to list5
adding object of class C to list list6.
The rest of the tries gives compiler errors, such us:
The method add(capture#1-of ? extends
A) in the type List is not applicable for the
arguments (A)
Why can't I add any object of class A,B,C to list1/2/3?
Why e.g. list4 accepts objects of classes A,B,C if they are supposed to be a super class of class A, as the list4 is defined?
"? extends A" means "some type derived from A (or A itself)". So for instance, a List<ByteArrayOutputStream> is compatible with List<? extends OutputStream> - but you shouldn't be able to add a FileOutputStream to such a list - it's meant to be a List<ByteArrayOutputStream>! All you know is that anything you fetch from the list will be an OutputStream of some kind.
"? super A" means "some type which is a superclass of A (or A itself)". So for instance, a List<OutputStream> is compatible with List<? super ByteArrayOutputStream>. You can definitely add a ByteArrayOutputStream to such a list - but if you fetch an item from the list, you can't really guarantee much about it.
See Angelika Langer's Generics FAQ for much more information.
The type definition List<? extends A> is not usable for a mutable List - the explanation given in Java generics Java Generics Pdf is
The add() method takes arguments of type E, the element type of the
collection.
When the actual type parameter is ?, it stands for some unknown type.
Any parameter
we pass to add would have to be a subtype of this unknown type. Since we
don’t know
what type that is, we cannot pass anything in.
However, when the typedef is List<? super A> then the type parameter ? is implicitly typed.
List<? extends A> list1
It is a list, whose type element could be any unknown subclass of A. For example, it could be a D subclass. Therefore, you can't add anything to it, it could be wrong...
List<? super A> list4
It is a list, whose type element could be A, or a superclass of A (does not exist in that case, except Object). Therefore, you can add A objects to it, or any subclass of A such as B or C.
It doesn't work that way.
You should use <? extends T> when you create function which argument is collection of unknown subtype of some type, and you want to fetch objects from that collection:
int sum(Collection<? extends Integer> collection) {
for (Integer : collection) {
// do sth
}
// ...
}
You cannot add new items to this collection, because you don't know which concrete type is this collection holding. All you know is that that type extends Integer.
You use <? super T> when you want to add new items of type T to collection and return that collection, but then you cannot guarantee what you can retrieve from it and you have to cast result of get() or check its type. You can safely add items of type T and subtypes, and retrieve items of type T.
List<? super B> allows you to use Lists of any supertype of B, i.e. list5 = new ArrayList<A>();
or list5 = new ArrayList<Object>();
You can safely add B (and subtypes) to every list that use supertypes of B, but you can not add any supertype of B. Imagine this:
public void myAdd(List<? super B> lst) {
lst.add(new Object()) //this is a supertype of B (compile time error)
}
...
ArrayList<A> list = new ArrayList<A>();
myAdd(list); //tries to add Object to a list of type A
How come in java we cannot do:
List<List<? extends Number>> aList = new ArrayList<List<Number>>();
Even though this is OK:
List<? extends Number> aList = new ArrayList<Number>();
Compiler error message is:
Type mismatch: cannot convert from ArrayList<List<Number>> to List<List<? extends Number>>
In Java, if Car is a derived class of Vehicle, then we can treat all Cars as Vehicles; a Car is a Vehicle. However, a List of Cars is not also a List of Vehicles. We say that List<Car> is not covariant with List<Vehicle>.
Java requires you to explicitly tell it when you would like to use covariance and contravariance with wildcards, represented by the ? token. Take a look at where your problem happens:
List<List<? extends Number>> l = new ArrayList<List<Number>>();
// ---------------- ------
//
// "? extends Number" matched by "Number". Success!
The inner List<? extends Number> works because Number does indeed extend Number, so it matches "? extends Number". So far, so good. What's next?
List<List<? extends Number>> l = new ArrayList<List<Number>>();
// ---------------------- ------------
//
// "List<? extends Number>" not matched by "List<Number>". These are
// different types and covariance is not specified with a wildcard.
// Failure.
However, the combined inner type parameter List<? extends Number> is not matched by List<Number>; the types must be exactly identical. Another wildcard will tell Java that this combined type should also be covariant:
List<? extends List<? extends Number>> l = new ArrayList<List<Number>>();
I'm not very familiar with Java syntax but it seems that your issue is this:
Covariance & Contravariance
You should definitely use the ? type wildcard when appropriate, do not avoid it as a general rule. For example:
public void doThingWithList(List<List<? extends Number>> list);
allows you to pass a List<Integer> or a List<Long>.
public void doThingWithList(List<List<Number>> list);
allows you to only pass arguments declared as List<Number>. A small distinction, yes, but using the wildcard is powerful and safe. Contrary to how it may seem, a List<Integer> is not a subclass, or is not assignable, from List<Number>. Nor is List<Integer> a subclass of List<? extends Number, which is why the code above does not compile.
Your statement does not compile because List<? extends Number> is not the same type as List<Number>. The former is a supertype of the latter.
Have you tried this? Here I'm expressing that the List is covariant in its type argument, so it will accept any subtype of List<? extends Number> (which includes List<Number>).
List<? extends List<? extends Number>> aList = new ArrayList<List<Number>>();
Or even this. Here the type parameter for the ArrayList on the right-hand side is the same as the type parameter on the left-hand side, so variance is not an issue.
List<List<? extends Number>> aList = new ArrayList<List<? extends Number>>();
You should be able to just say
List<List<Number>> aList = new ArrayList<List<Number>>();
I tend to avoid the ? type wildcard whenever possible. I find that the expense incurred in type annotation is not worth the benefit.
List<List<? extends Number>> aList = new ArrayList<List<? extends Number>>();
aList.add(new ArrayList<Integer>());