Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Upper-Bounded and Lower-Bounded Wildcards in return type of Java Generic method

I was trying to solve a problem where I am not able to understand part of the answer.

Following is the class BackLister:

public class BackLister {
    // INSERT HERE
    {
        List<T> output = new LinkedList<T>();
        for (T t : input)
            output.add(0, t);
        return output;
    }
}

The question asks which can be inserted at // INSERT HERE in the BackLister class to compile and run without error?

Following are the options:

A. public static <T> List<T> backwards(List<T> input)
B. public static <T> List<T> backwards(List<? extends T> input)
C. public static <T> List<T> backwards(List<? super T> input)
D. public static <T> List<? extends T> backwards(List<T> input)
E. public static <T> List<? super T> backwards(List<T> input)
F. public static <? extends T> List<T> backwards(List<T> input)
G. public static <? super T> List<T> backwards(List<T> input)

I understand that that A and B are correct, as for for (T t : input) to work the elements in input should be of type T or subtype of T.

But I am not able to understand why D and E options are correct?

I understand the following:

  1. public static <T> List<? extends T> backwards(List<T> input) means that the return type should be a List of T or subclass of T.
  2. public static <T> List<? super T> backwards(List<T> input) means that the return type should be a List of T or superclass of T.

Could somebody help me understand it?

like image 919
skip Avatar asked Jan 28 '23 05:01

skip


2 Answers

There is difference between each of them and I'm going to explain most of them. Let's start with our example. I use this class hierarchy:

class Food {}
class Apple extends Food {}
class Orange extends Food {}
class RedApple extends Apple {}

List<Food> listFood = new ArrayList<>();
List<Apple> listApple = new ArrayList<>();
List<Orange> listOrange = new ArrayList<>();
List<RedApple> listRedApple = new ArrayList<>();

ow start with first one:

A. public static <T> List<T> backwards(List<T> input)

This method will only accept List<T> and return List<T> and you can not send listApple and return listRedApple. (however your return list can contain RedApple because it extends Apple but type of list must be List<Apple> and nothing else)

B. public static <T> List<T> backwards(List<? extends T> input)

You can send listRedApple and return listApple but you know that listRedApple is "? extend Apple" so in the method body java recognize T as Apple. Then if you use can add elements in listRedApple which sent as argument, you can add Apple in listRedApple which is not true!!! so compiler avoid it and give compile error. In B you can only read elements (and get it as T) but you can not add anything to it.

C. public static <T> List<T> backwards(List<? super T> input) 

You can send listApple and then in method body you can add anything extends Apple because compiler see T as Apple and in a list of anything which is super of T, you can add anything extends Apple.
However this time, you can not read anything because you don't know its type except you get it as Object. (It is a list of "? super T")

As you see here there is difference between ? super and ? extend. one of them give you write access and other give you read access. This is the real use of wildcard.

D. public static <T> List<? extends T> backwards(List<T> input)
E. public static <T> List<? super T> backwards(List<T> input)   

If you send listApple then you return List<? extends Apple> but you can's assign it to any of listFood or listApple or listRedApple because List<? extends Apple> maybe contain Apple or RedApple or something else and we can't assign it to any List<T> because then we can add T to that list and maybe T and ? extends T is not same. this is same for both D and E. You can assign it to List<? extends Apple> for 'E and List<? super Apple> for D and send them to a method which need them as parameter.

F. public static <? extends T> List<T> backwards(List<T> input)
G. public static <? super T> List<T> backwards(List<T> input)

Give compile errors because wildcard can not used like this.

I hope this help you.
If something is wrong, any comment is appreciated.

like image 124
Amin Avatar answered Jan 29 '23 20:01

Amin


Options D and E are valid because there exist a super-subtype relationships among generic types that allow you to define a larger set of types that the method can accept or return.

Consequently, the following is valid (D):

public static <T> List<? extends T> backwards(List<T> input) {
    return List.of();
}

// there exist a super-subtype relationships among List<? extends Number> and List<Long>
List<? extends Number> list = backwards(List.<Long>of(1L, 2L));

because the type Long is a member of the type family that the wildcard ? extends Number denotes (the family of types that are subtypes of Number and the type Number itself).

The next code snippet is also valid (E):

public static <T> List<? super T> backwards(List<T> input) {
    return List.of();
}

List<? super Long> ints = backwards(List.<Long>of(1L, 2L));

because the type Long is a member of the type family that the wildcard ? super Long denotes (the family of types that are supertypes of Long and the type Long itself).

So, your understanding is correct.

Further reading.

like image 41
Oleksandr Pyrohov Avatar answered Jan 29 '23 19:01

Oleksandr Pyrohov