Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What method declaration accepts bounded and unbounded multi-level Generics?

Tags:

Given:

public class Testcase {

    public static <E> List<List<E>> transform(List<List<E>> list) {
        return list;
    }

    public static <E> List<List<? extends E>> transform2(List<List<? extends E>> list) {
        return list;
    }

    public static void main(String[] args) {
        List<List<Integer>> known = new ArrayList<>();
        List<List<? extends Number>> unknown = new ArrayList<>();

        transform(known);    // works
        transform(unknown);  // fails

        transform2(known);   // fails
        transform2(unknown); // works
    }
}

The compiler accepts transform(known) but complains:

cannot infer type-variable(s) E
    (argument mismatch; List<List<? extends Number>> cannot be converted to List<List<E>>)
  where E is a type-variable:
    E extends Object declared in method <E>transform(List<List<E>>)

for transform(unknown). I get the opposite problem for transform2(). I've consulted PECS and I believe that transform() is the correct method declaration but I can't for the life of my figure out how to get a single method to handle both cases.

Note that this problem only occurs for multi-level Generics. List<? extends Number> works just fine. The problem is not specific to Lists. You'll get it for Future<Task<X>> and so on.

What method declaration will handle both bounded and unbounded Generics? And if it's not possible, why?

like image 963
Gili Avatar asked Mar 06 '18 05:03

Gili


People also ask

What is bounded and unbounded wildcards in generics?

Bounded and unbounded wildcards in Generics are two types of wildcards available on Java. Any Type can be bounded either upper or lower of the class hierarchy in Generics by using bounded wildcards.

What is bounded generics in Java?

If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class. These are known as bounded-types in generics in Java.

What is unbounded Java?

Unbounded wildcards An unbounded wildcard is the one which enables the usage of all the subtypes of an unknown type i.e. any type (Object) is accepted as typed-parameter. For example, if want to accept an ArrayList of object type as a parameter, you just need to declare an unbounded wildcard.

What are type bounds in Java?

A bound is a constraint on the type of a type parameter. Bounds use the extends keyword and some new syntax to limit the parameter types that may be applied to a generic type. In the case of a generic class, the bounds simply limit the type that may be supplied to instantiate it.


1 Answers

The most specific type for which this works seems to be ? extends List<? extends ?>:

class Testcase {

    public <E> List<List<E>> transform(List<List<E>> list) {
        return list;
    }

    public <E> List<List<? extends E>> transform2(List<List<? extends E>> list) {
        return list;
    }

    public <E> List<? extends List<? extends E>> transform3(List<? extends List<? extends E>> list) {
        return list;
    }    

    public void test(String[] args) {
        List<List<Integer>> known = new ArrayList<>();
        List<List<? extends Number>> unknown = new ArrayList<>();

        transform(known);    // works
        // transform(unknown);  // fails

        // transform2(known);   // fails
        transform2(unknown); // works

        transform3(known);
        transform3(unknown);
    }
}

Here is an explanation why this makes sense.

A List<? extends Number> is clearly not a List<E> for any E, because it doesn't have to be able to insert instances of the most general E, thus the first definition fails.

A List<Integer> conforms to List<? extends Number>, but this does not help, because these types are still unequal, therefore types List<List<Integer>> and List<List<? extends Number>> are completely unrelated. Therefore, the second definition also fails.

What you want instead is the use-site least upper bound of List<List<Integer>> and List<List<? extends Number>>. You can obtain it using the following rule: the use-site-LUB of List<A> and List<B> is:

USLUB(List<A>, List<B>) = List<? extends USLUB(A, B)>

Now, step by step:

  1. For A = Integer and B = ? extends Number, the least upper bound is ? extends Number, because Integer conforms to ? extends Number.
  2. For List<Integer> and List<? extends Number> the least upper bound becomes ? extends List<? extends Number>
  3. Thus the final result is List<? extends List<? extends Number>>.

Hurray for use-site variance ;)

like image 64
Andrey Tyukin Avatar answered Sep 22 '22 12:09

Andrey Tyukin