In java generic I understood what are the meanign of wild card, super and extends, but didn't get why does not allow me to add anything, and why allows me to add upto SomeType in hierarchy, but not above in the hierarchy?
class Animal {}
class Cat extends Animal{}
following method can take list of Animal or sub of Animal i.e Cat, but nothing else
and I am not allowed to add anything, if try to add, compiler stops me why ?
void addAminal(List<? extends Aminal> aList){
aList.add(new Cat()); // compiler error
aList.add(new Animal()); // compiler error
}
Now following method can take any list of Animal or any super type of Animal, but no sub type of Animal, and I can add objects upto Animal or lower in hierarchy, so when I try to add Object, compiler complains why ?
void addAnimal(List<? super Animal> aList){
aList.add(new Animal()); // no error
aList.add(new Cat()); // no error
aList.add(new Object()); // compiler error why ?
}
Thanks
Arya
Suppose you defined a new class:
class Tabby extends Cat {}
And then you did the following:
List<Tabby> aList = new ArrayList<Tabby>();
addAnimal(aList);
There's no surprise that this list should not have an Animal or even a Cat that isn't a Tabby, yet if the compiler didn't flag the error, that's what you would have.
The reason is that hou've specified addAnimal to take a list of something that extends Animal, but that something could be highly restrictive. This, however, would compile:
void addAnimal(List<Animal> aList){
aList.add(new Cat()); // OK
aList.add(new Animal()); // OK
}
The use of super also would work, because an instance of either Cat or Animal is an instance of any superclass of Animal.
List<? extends Animal> means List<X> where X is an unknown subtype of Animal.
Therefore it has methods
void add(X item);
X get(int i);
You can't call add(cat), because we don't know if Cat is a subtype of X. Since X is unknown, the only value that we knows is a subtype of X is null, so you can add(null) but nothing else.
We can do Animal a = list.get(i), because the method returns X and X is a subtype of Animal. So we can call get(i) and treat the return value as an Animal.
Conversely, List<? super Animal> means List<Y> where Y is an unknown super type of Animal. Now we can call add(cat), because Cat is a subtype of Animal, Animal is a subtype of Y, therefore Cat is a subtype of Y, and add(Y) accepts a Cat. On the other hand, Animal a = list.get(0) won't work now, because Animal is not a super type of the return type Y; the only known super type of Y is Object, so all we can do is Object o = list.get(0).
The generics only allow you to add an object of the type (or a subtype) of the type given as type parameter. If you put <? extends Animal> it means the list has SOME type that is a subclass of animal. Since you are trying to add a Cat to it, you have to be sure that it is indeed a list of Cats, and not of Dogs.
Basically, when you use a wildcard you will not be able to add new items to such a list (note: I don't have full knowledge and this might not be fully correct, but it seems like this. Forgive me if I'm wrong)
If you want to be able to add any Animal to the list, just use List<Animal>.
Well, When you say ArrayList< ? extends Animal > you are specifying that this list will contain any specific type (as ? refers to a specific/definite type) that is of type Animal or anything inherited from Animal but something definite. So ultimately, as the generics are implemented with Eraser concept (which replaces every generic type in the program by a non-generic upper bound), this list is supposed to contain a specific type but due to ( < ? extends Animal >) you don't know which specific type is that. And hence you are not allowed to add even though types are inherited from Animal.
But when you say ArrayList< ? super Animal >, it means the arraylist contains specific type derived from Animal that is, objects whose base or super type is Animal. Hence it is safe to pass a Animal or anything derived from Animal into this list. The list is treated that way and it is allowed to add objects as mentioned. And hence it works.
Hope it helps!
Related
I know that you use <? extends> wildcard when you only get values out of a collection.
Suppose there's Animal superclass and Dog and Cat subclasses. Basically I want to have a list that contains dogs and cats. I found out I can do the following:
List<? extends Animal> animalList;
List<Dog> dogList = getDogs();
List<Cat> catList = getCats();
animalList = Stream.concat(dogList.stream(), catList.stream()).collect(Collectors.toList())
// Do things according to its type
for (Animal animal : animalList) {
if (animal instance of Dog) {...}
if (animal instance of Cat) {...}
}
The above code compiles. So does it violate the rule in the sense that I'm writing values to animalList?
Stream.concat(dogList.stream(), catList.stream()).collect(Collectors.toList())
This creates a List<Animal>. Crucially, not a List<? extends Animal>. Try it:
List<Animal> = ... all that ...
works fine.
List<Animal> doesn't mean that everything in it was made using literally new Animal(). You can have a List<Animal> that contains solely Cat instances. Those are all animals, that's fine.
The 'point' of ? extends and all that is when you deal with the lists themselves, not with the things within them. Here's specifically why:
List<Animal> x = new ArrayList<Cat>();
x.add(new Dog());
Cat z = x.get(0);
Triple check the above code but it explains exactly why ? extends (and ? super) exists. Generics must be invariant, anything else leads to broken code. The above code must not compile because it makes no sense. As written, it indeed doesn't compile - line 1 isn't allowed. You can 'make it work' by writing List<? extends Animal> x = new ArrayList<Cat>() which compiles fine, but now x.add(new Dog() won't.
The difference is this:
a List<Animal> variable is pointing at some list that is actually some list of specifically <Animal> and not some subtype or supertype of Animal. It might be a LinkedList<Animal> or an ArrayList<Animal>, that's fine, but not an ArrayList<Cat>. With that known, when you 'read' from it, you get Animal objects out, and when you write to it, Animal is fine.
a List<? extends Animal> variable on the other hand is some list that is of Animal or some subtype of Animal. It could be a LinkedList<Dog>. Given that fact, when you read, Animal is fine (Animal f = thatList.get(0) compiles fine), but you can't write anything to it. It might be a list of Dogs, but it could also be a list of Cats, and absolutely no object is therefore save (Except, trivially, literally the expression null, written out like that: thatList.add(null) compiles. And isn't useful, of course).
You assign your List<Animal> expression to a variable of type List<? extends Animal> which is fine. And needless; List<Animal> x = Stream.concat.... would have worked just as well.
Found a difference between collect(Collectors.toList()) and Stream.toList(). See
class Animal { }
class Cat extends Animal { }
record House(Cat cat) { }
class Stuff {
public static void function() {
List<House> houses = new ArrayList<>();
List<Animal> animals1 =
houses.stream()
.map(House::cat)
.collect(Collectors.toList()); // ok
List<Animal> animals2 =
houses.stream()
.map(House::cat).toList(); // compile error
List<Animal> animals3 =
houses.stream()
.map(House::cat)
.map(cat -> (Animal) cat).toList(); // ok
}
}
The collect(Collectors.toList()) is able to give me a List of Animal or List of Cat. But the Stream.toList() can only give a List of Cat.
The question is there any way to make Stream.toList() work. In my real world example I have a class which overrides shutdownNow, which returns a list of Runnable, so my class was calling something.stream().collect(Collectors.toList()), but something.stream().toList() returns a list of MyRunnable.
A part of me wishes they declared the function as default <U super T> List<U> toList() instead of default List<T> toList(), though strangely that is a compile error on my machine (my compiler seems to be ok with U extends T, not U super T).
There is a simple answer here.
Start with
var stream = houses.stream().map(House::cat)
Here, stream has type Stream<Cat>. The Stream::toList method gives you a list of the stream element type, which here is Cat. So stream.toList() is of type List<Cat>. There's no choice here.
Collector has multiple type variables, including the type of the input element, and the type of the output result. There is a lot of flexibility to create a Collector that takes in Cat and produces List<Cat>, List<Animal>, Set<Animal>, etc. This flexibility is partially hidden by the genericity of Stream::collect (and also the generic method Collectors::toList); inferring the generic type parameters for this method can take into account the desired result type on the LHS. So the language papers over the gap between Cat and Animal for you, because there's another level of indirection between the stream and the result.
As #Eugene pointed out, you could get a more general type out:
List<? extends Animal> animals2 = houses.stream().map(House::cat).toList()
This has nothing to do with streams; it is just because List<? extends Animal> is a supertype of List<Cat>. But there's an easier way. If you want a List<Animal>, and you want to use toList(), change the stream type to a Stream<Animal>:
List<Animal> animals = houses.stream().map(h -> (Animal) h.cat()).toList()
Stream::map is also generic, so by having the RHS of the lambda be Animal, not Cat, you get a Stream<Animal> out, and then toList() gives you a List<Animal>.
You could also break it up, if you like that better:
List<Animal> animals = houses.stream().map(House::cat)
.map(c -> (Animal) c).toList()
What is tripping you up is that, because collect is a generic method (and so Collectors::toList), there is additional flexibility to infer a slightly different type, whereas in the simpler stream, everything is more explicit, so if you want to adjust the types, you have to do that in imperative code.
That is impossible to achieve.
Collectors::toList has a declaration of ? extends T. On the other hand Stream::toList returns a List<T>, you are stuck.
You can workaround that (partially), via:
List<? extends Animal> animals2 = houses.stream().map(House::cat).toList();
What you are thinking is U super T, but that is not supported, unfortunately. People occasionally found good real use cases for it - but support is not there.
this a wildcard question.
my purpose is to make a list that contains a class that has a generic type with extended examples:
so this is the structure:
public class Event<T extends ActionType>{
}
public abstract class ActionType{
}
//**many** classes that extends ActionType class
that's a list that hold classes that extends from ActionType
private List<Event<ActionType>> list = ArrayList<>();
Event<RightClick> event = new Event<>(xPosition, yPosition, delay, new RightClick(robot), clicks, robot);
list.add(event);
I know that's I cant do this to make sure that I can add the extended items:
private List<Event<ActionType>> list = ArrayList<>();
but what I can do to add the items to the same list.
to fix my problem I used the wildcard selector ?
List<Event<? extends ActionType>>
Explanation
Generics are invariant. A List<Event<ActionType>> will not accept Event<RightClick>, only Event<ActionType>.
Understand generics and adjust your generic type restrictions.
If generics would be covariant, then you could give someone who expects a List<Animal> a List<Dog> and then add Cats to it. Which would cause heap corruption as dogs.get(0) could suddenly be a Cat.
An example:
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
List<Animal> animals = dogs; // pretend this would work
animals.add(new Cat()); // would be legit
Dog dog = dogs.get(1); // should be safe, but is cat, heap corruption
A List<Animal> explicitly says that this list must accept all animals, also cats. But a List<Dog> is restricted to dogs only. The two lists behave differently, they have different restrictions, hence you can not use one for another. Unlike a Dog who is an Animal, a List<Dog> is not an List<Animal>, that is what is meant by co- and invariance.
Solution
The correct tool to "accept any ActionType, I do not care" are wildcards. So either go with
List<Event<? extends ActionType>>
or just
List<Event<?>>
since the Event class already specifies the T extends ActionType restriction.
With that type you will be able to add all sorts of Events to it:
Event<RightClick> rightClick = ...
Event<LeftClick> leftClick = ...
Event<MiddleClick> middleClick = ...
list.add(rightClick);
list.add(leftClick);
list.add(middleClick);
As a consequence of the ? wildcard you will not be able to know the actual type at compile-time anymore, so:
Event<?> event = list.get(0); // unknown which exact type
All you know of ? is that it is at least extends ActionType, so you will be able to use all sorts of methods that are given by ActionType, but nothing introduced only in RightClick for example. That would require an explicit cast (guarded by an instanceof check), although I would question your design if you have to use right-click specific things there.
I am trying to digest a standard example of using a covariant/contravariant type argument for a Collection and then trying to work out why certain methods behave in a way they do. Here is my example and my (potentially wrong) explanation, and in case of confusion, questions:
List<? extends MyObject> l = new ArrayList<>();
x = l.get(0);
type of x is MyObject -> this is because compiler realizes that that the upper bound on the type is MyObject and can safely assume this type.
l.add(new MyObject()); //compile error
The error is caused because, although the upper-bound type of the collection is known, the ACTUAL type is not known (all the compiler knows is that it is the subclass of MyObject). So, we could have a List<MyObject>, List<MyObjectSubclass> or god knows what other subtype of MyObject as type parameter. As a result, in order to be sure that objects of wrong type are not stored in the Collection, the only valid value is null, which is a subtype of all types.
Conversly:
List<? super MyObject> l = new ArrayList<>();
x = l.get(0)
type of x is Object, as compiler only knows about the lower-bound. Therefore,. teh only safe assumption is the root of type hierarchy.
l.add(new MyObject()); //works
l.add(new MyObjectSubclass()); // works
l.add(new Object()); //fails
The above final case is where I am having problems and I am not sure whether I get this right. COmpiler can expect any list with a generic type of MyObject all the way to the Object. So, adding MyObjectSubclass is safe, because it would be OK to add to List<Object> even. The addition of Object, would however, violate List<MyObject>. Is that more or less correct? I would be glad to hear more technincal explanation if anyone has it
Generics are neither covariant nor contravariant. They are invariant.
Wildcards can be used to facilitate usage of parameterized types. About 95% of what you need to know about them has been summed up by Mr. Bloch in Effective Java (must read) with the PECS rule (Producer Extends Consumer Super).
Assume
interface Owner<T> {
T get();
void set(T t);
}
and the usual Dog extends Animal example
Owner<? extends Animal> o1;
Animal a = o1.get(); //legal
Dog d = (Dog) o1.get(); //unsafe but legal
o1.set(new Dog()); //illegal
Conversely:
Owner<? super Animal> o1;
o1.set(new Dog()); //legal
Animal a = o1.get(); //illegal
To answer more directly List<? super Dog> is a list that consumes (adds in this case) Dog instances (meaning it will consume a Poodle instance as well).
More generally, the methods of a Foo<? super Bar>instance that are defined to accept an argument of Foo's type parameter, can be called with any object referenced compile time with Bar or a sub-type of Bar.
Don’t get fooled by thinking about the behavior of objects you have just created. There’s no sense in giving them wildcards. Wildcards are there to give freedom to method parameters. Look at the following example:
public static <T> void sort(List<? extends T> list, Comparator<? super T> c)
This means there must be a type T for which the list guarantees that all elements are of type T (might be a subclass) and that the provided Comparator implementation can handle elements of T (might be more abstract).
So it is permitted to pass in a List<Integer> together with a Comparator<Number>.
The code below makes complete sense to me - its about adding an element of some type which is supertype of type T and type S is definitely such a super type , so why the compiler refuses to add 'element' into the collection ?
class GenericType<S,T extends S>{
void add1(Collection<? super T> col ,S element ){
col.add(element); // error
// The method add(capture#9-of ? super T) in the type
// Collection<capture#9-of ? super T> is not applicable for the arguments (S)
}
}
Thake an example, if A <- B <- C where <- means that is the supertype, then if S = B and T = C you cannot add an instance of S to a collection of T.
A supertype of T may be the supertype or a subtype of another supertype of T (in this case S).
Collection<? super T> does not mean "a collection that can contain T and any superclass of it" - it's actually not possible to formulate that restriction. What it means is "a collection that can only contain instances of some specific class which is a superclass of T" - basically it ensures that you can add a T to the collection.
The method can be called with a Collection<T>, yet you want to add an S to it.
new GenericType<Object,Integer>().add1(new ArrayList<Integer>(), "");
You are trying to put an element of type S into a collection of type T. Generics aren't polymorphic.
You have to take notice of 2 problems here. you are trying to create an Collection of type concreteObject extends Object and are adding an object
So when you have
Car extends Vehicle{}
ElectricCar extends Car{}
you are trying to do
Collection<? extends Car> collection;
collection.add(new Vehicle());
The second problem lies with the non-polymorphism nature of Generics. See this great explanation -> Is List<Dog> a subclass of List<Animal>? Why aren't Java's generics implicitly polymorphic?