Template parameter difference between Collectors.toList and Stream.toList - java

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.

Related

Java Generics in Stream

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.

generic misunderstanding when tying to add to list

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.

Java API class having a method with a meaningless type wildcard

In Java 8, the class java.util.Optional (javadoc) class offers functionality of the "Maybe" Monad or the Option Type.
More directly:
public final class java.util.Optional<T> extends Object
A container object which may or may not contain a non-null value. If a
value is present, isPresent() will return true and get() will return
the value.
One of the methods is:
<U> Optional<U> map(Function<? super T,? extends U> mapper)
If a value is present, apply the provided mapping function to it, and if
the result is non-null, return an Optional describing the result.
The question is simply why does map() use a type wildcard on the U type.
My understanding of type wildcarding is simply that:
? super T designates some type from the set of classes given by the subclassing path from Object to T (both Object and T included) union the set of interfaces found on any subinterfacing path pulled in "as a sidedish" via implements.
And ? extends U simply designates some type from the set of classes that extend U (Uincluded).
So one could just write:
<U> Optional<U> map(Function<? super T,U> mapper)
without loss of information.
Or not?
Not quite.
A Function<? super T, U> is not the same as a Function<? super T, ? extends U>.
For example, I could still get an Optional<CharSequence>, even if I passed a Function<Object, String> to the method. If the method was defined as <U> Optional<U> map(Function<? super T, U> mapper), then this would not be possible.
That is because generics are invariant: <T> is not the same as <? extends T>. That's a design decision implemented in the Java language.
Let's see how Jon Skeet explains what would happen if generics would not be invariant:
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
public void ouch() {
List<Dog> dogs = Arrays.asList(new Dog(), new Dog());
List<Animal> animals;
// This would be legal, right? Because a list of dogs is a list of animals.
List<Animal> animals = dogs;
// This would be legal, right? Because a cat could be added to a
// list of animals, because a cat is an animal.
animals.add(new Cat());
// Unfortunately, we have a confused cat.
}
Although I'm not entirely sure what you mean in your comments, I'll try to elaborate.
If you have full control over the Function you provide, it doesn't matter whether the method's signature is Function<? super T, U> or Function<? super T, ? extends U>, you'll just adapt your Function accordingly. But the authors of the method probably wanted the method to be as flexible as possible, allowing one to provide a Function with its second parameter to be also a subclass of U, instead of only U itself. Actually you widen its lower bound from U to a certain subtype of U.
So the function should really read <U> Optional<? the-most-general-but-fixed-supertype-of U> map(Function<? super T, U> mapper) but expressing it that way would be awkward.
I would be indeed awkward. In addition, there is a difference between your proposed notation and the actual method signature of map(), which involve implications of lower bounds and upper bounds.
Read more:
A picture explaining the principle "Producer Extends Consumer Super"
What is the use of saying <? extends SomeObject> instead of <SomeObject>?
Upper and lower bounds

Java covarianc/contravariance with add/get

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>.

java generic and wild card

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!

Categories

Resources