Reputation: 1008
Suppose I would like to write a method that takes a list and a Function
returning another generic type, say Optional
. A concrete example for such a method would be one that applies the function to all elements in the list, and returns a list of all elements that didn't result in an empty Optional
.
To the best of my understanding, here is how I would write out this method:
public <I, O> List<O> transform(List<I> list, Function<? super I, Optional<? extends O>> function) {
List<O> result = new ArrayList<>();
for (I element : list) {
Optional<? extends O> optional = function.apply(element);
if (optional.isPresent()) {
result.add(optional.get());
}
}
return result;
}
The reason I used wildcard arguments for Function
are as follows:
Function
is exactly I
. It should be fine to pass anything that takes a parent type of I
.Optional
that the Function
returns doesn't have to be exactly O
. It should be fine to return an Optional
of a subtype of O
.Let's test this by first defining two simple types:
public class Animal {}
public class Cat extends Animal {}
Now suppose we're starting with a list of Cat
s:
List<Cat> catList = new ArrayList<>();
According to the two points I made about transform
taking wildcard arguments above, I would like to transform this list into another list of Animal
s using the following method:
public Optional<Cat> animalToCat(Animal cat) {
return Optional.empty();
}
This indeed works when I pass a method reference to animalToCat
:
List<Animal> animalList = transform(catList, this::animalToCat); // works!
However, what if I don't directly have this method reference available, and would like to store the method in a variable first before passing it to transform
later?
Function<Animal, Optional<Cat>> function2 = this::animalToCat;
List<Animal> animalList2 = transform(catList, function2); // does not compile!
Why does the line resulting in animalList
compile, but the one resulting in animalList2
doesn't? Through trial and error, I figured out that I can indeed make this compile by changing the type of the variable I'm assigning my method reference to to any of the following:
Function<Animal, Optional<? extends Animal>> function3 = this::animalToCat;
List<Animal> animalList3 = transform(catList, function3); // works
Function<Cat, Optional<? extends Animal>> function4 = this::animalToCat;
List<Animal> animalList4 = transform(catList, function4); // works
Function<? super Cat, Optional<? extends Animal>> function5 = this::animalToCat;
List<Animal> animalList5 = transform(catList, function5); // works
So it seems to be okay to assign the animalToCat
method reference, which is clearly one taking an Animal
and returning a Optional<Cat>
, to other Function
types. However, taking the existing animalToCatFunction2
and assigning it to the other types also fails:
animalToCatFunction3 = animalToCatFunction2; // does not compile!
animalToCatFunction4 = animalToCatFunction2; // does not compile!
animalToCatFunction5 = animalToCatFunction2; // does not compile!
I'm very confused as to why it would be okay to treat the this::animalToCat
method reference in a way that would make it seem like the returned Optional<Cat>
is a covariant type, but that behavior suddenly breaks as soon as the reference is assigned to a variable with a specific type. Is my definition of transform
wrong?
Upvotes: 3
Views: 232
Reputation: 7212
See STU's reply for the underlying reason.
To get your example to work, you need to help the Java type system a bit by using more type parameters:
<A, B, C extends B, D extends A> List<B> transform(List<D> list, Function<A, Optional<C>> function) {
List<B> result = new ArrayList<>();
for (D element : list) {
Optional<C> optional = function.apply(element);
if (optional.isPresent()) {
result.add(optional.get());
}
}
return result;
}
Since the dynamic sub-type of the incoming list can be different to the outgoing one, it's useful to add another sub-type to the example:
class Dog extends Animal {}
Then the example code is:
List<Dog> dogList = new ArrayList<>();
List<Animal> animalList = transform(dogList, this::animalToCat);
Function<Animal, Optional<Cat>> function2 = this::animalToCat;
List<Animal> animalList2 = transform(dogList, function2);
EDIT: Modified the type constraints a bit to make it work with the example in the comment as well:
Optional<Animal> catToAnimal(Cat cat) {
return Optional.empty();
}
Function<Cat, Optional<Animal>> function2b = this::catToAnimal;
List<Animal> animalList2b = transform(catList, function2b);
Upvotes: 1
Reputation: 121
I believe the reason this does not compile:
Function<Animal, Optional<Cat>> function2 = this::animalToCat;
List<Animal> animalList2 = transform(catList, function2);
whereas this does:
List<Animal> animalList = transform(catList, this::animalToCat);
is that bare method references have a type that is not representable in the user-visible type system.
You can think of this::animalToCat
as having the "magic" type Function<contravariant Animal, covariant Optional<Cat>>
, but as soon as you convert to the user-defined type Function<Animal, Optional<Cat>>
you lose the information about the variance of raw functions themselves.
At that point, you can't assign a Function<Animal, Optional<Cat>>
to a Function<Animal, Optional<? extends Animal>>
for the same reason you can't assign a Function<Optional<Cat>, Animal>
to a Function<Optional<? extends Animal>, Animal>
.
C.f. Function1[-T1, +R]
in Scala.
Upvotes: 1