1

There is class called Foo as following:

public class Foo<E> {

    private List<E> ls;

    public Foo(List<E> ls) {
        this.ls = ls;
    }

    public void add(E l) {
        this.ls.add(l);
    }

    public <F> Foo<F> map(Function<? super E, ? extends F> f) {
        Foo<F> res = new Foo<F>(new ArrayList<F>());
        for (E l : ls) {
            res.add(f.apply(l));
        }
        return res;
    }
}

Then, I call from main:

//Main.java
Foo<Integer> foo1 = new Foo<>(Arrays.asList(1,2,3,4));
Foo<String> foo2 = foo1.map(a -> a.toString());

I want to see how defining the function as Function<? super E, ? extends F> gives an advantage over Function<E,F>? Is Function<? super E, ? extends F> for has the maximum flexibility? Could you please show it in a example by changing Main.java?

knittl
  • 246,190
  • 53
  • 318
  • 364
vortex
  • 117
  • 7
  • @knittl, unfortunately, it did not answer my question. My question is to find a case which `? super E, ? extends F` provide us more flexibility. if it is clear to you could you please show it in the example? My example in `Main.java` can work with `Function`. – vortex May 15 '22 at 18:26
  • I have not provided an answer, so I don't know what I didn't answer? – knittl May 15 '22 at 18:30
  • @knittl Somebody marked my question as duplicate and referred to the PECS article as the answer. I assumed you have done it :) – vortex May 15 '22 at 18:35
  • @knittl do you know how can I get rid of the duplicate flag that people can answer my question? – vortex May 15 '22 at 18:45
  • Why does it not answer your question? [edit] the question to explain that and it might get re-opened. Simplified, a `Function super T, ? extends R>` can take a `animal -> new Tiger(animal.getName())` lambda, while `Function` can not (because it can only be passed a `cat -> new Cat(cat.getName())`) – knittl May 15 '22 at 18:51
  • The answer was not correct. `Function` can change any type to any other type. so, I assume `Function` can change cat to tiger as well. – vortex May 15 '22 at 19:55
  • `foo1.map((Number a) -> a.toString())` will not work with your given class if your type parameters are not co-/contravariant, because `Number` is not `Integer` (but `Integer implements Number`). – knittl May 15 '22 at 19:58
  • thank you. Your example can also run with `Function super E, F>`. Could you please give an example which `? extends F` is also necessary? – vortex May 15 '22 at 20:02

1 Answers1

2

It's a bad idea to try to reason about generics with final classes like String and Integer.

Try a hierarchy like A > B > C

class A {}
class B extends A {}
class C extends B {}

Then an example like

Foo<B> foo1 = new Foo<>(new B());
Function<A, C> aToC = a -> new C();
Foo<B> foo2 = foo1.map(aToC);

This won't work without both super E and extends F. The function takes an A - foo1 is a B, but A is a super class of B - and returns a C - foo2 is expecting a B, but C extends B.

If your map function were simply declared as

public <F> Foo<F> map(Function<E, F> f) {

then the above snippet wouldn't compile. The function would have to be Function<B, B>.

Hopefully that demonstrates why the more complex signature is more flexible.

Michael
  • 41,989
  • 11
  • 82
  • 128