Question

As per a literature I read,we have juicy fruits implementign the following interface:

public interface Juicy<T> {
    Juice<T> squeeze();
}

Using bounded type variables, following method would taks a bunch of fruits and squeeze them all:

<T extends Juicy<T>> List<Juice<T>> squeeze(List<T> fruits);

Now we need lower siblings as below to work too:

class Orange extends Fruit implements Juicy<Orange>;
class RedOrange extends Orange;

So I would expect the method to look as follows:

<T extends Juicy<T>> List<Juice<? super T>> squeeze(List<? extends T> fruits);

Instead I find the method signature to be as below:

<**T extends Juicy<? super T>>** List<Juice<? super T>> squeezeSuperExtends(List<? extends T> fruits);

What explains this difference?

Was it helpful?

Solution

The <? super T> within <T extends Juicy<? super T>> is there so that RedOrange, which is a subclass of Juicy<Orange> can be within its bound.

Imagine without the <? super T> first:

public <T extends Juicy<T>> List<Juice<T>> squeeze(List<T> fruits) {...

Now T must be a Juicy<T>. The class Orange is a Juicy<T>, it's a Juicy<Orange>. But the class RedOrange is not a Juicy<T>. It's not a Juicy<RedOrange>; it's a Juicy<Orange>. So when we attempt to call squeeze:

List<RedOrange> redOranges = new ArrayList<RedOrange>();
List<Juice<RedOrange>> juices = squeeze(redOranges);

we get the following compiler error:

Inferred type 'RedOrange' for type parameter 'T' is not within its bound; should implement 'Juicy<RedOrange>'.

If we place the <? super T>, that allows the type parameter for Juicy to be a superclass of T. This allows RedOrange to be used, because it's a Juicy<Orange>, and Orange is a superclass to RedOrange.

public <T extends Juicy<? super T>> List<Juice<T>> squeeze(List<T> fruits) {...

Now the call to squeeze above compiles.

EDIT

But what if we want to squeeze a List<Juice<Orange>> from a List<RedOrange>? It got a little tricky, but I found a solution:

We need a second type parameter to match Orange in the squeeze method:

public <S extends Juicy<S>, T extends Juicy<S>> List<Juice<S>> squeeze(List<T> fruits)

Here, S represents Orange, so that we can return List<Juice<Orange>>. Now we can say

List<RedOrange> redOranges = new ArrayList<RedOrange>();
List<Juice<Orange>> juices = squeeze(redOranges);

OTHER TIPS

It seems to me the easiest way to think about this is to briefly ignore the relationship between the type of fruit and the type of fruit juice it produces. That link is established in the class declaration; we don't need it to squeeze a bunch of Juicys.

In other words, just parameterize on the type of Juice that the Juicys will produce:

<T> List<Juice<T>> squeeze(List<? extends Juicy<? extends T>> fruits);

Here we produce a list of juices based on the common super type of produced Juice (i.e. the parameter of Juicy), not the common super type of fruit.

Then we get the following:

//works
List<Juice<Orange>> b = squeeze(Arrays.asList(new Orange(), new RedOrange()));

//fails as it should 
List<Juice<RedOrange>> d = squeeze(Arrays.asList(new RedOrange()));
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top