4

I have some code that returns a wildcard parameterized type. I'm trying to pass this to a parametric method, but I get a compiler error. Can someone explain to me why the types are not matched, and what the best way to fix this is?

static <L extends List<T>,T extends Number>
void useList(List<L> list) {}

public static void main(String[] args) {
    List<List<? extends Number>> list = null;
    useList(list);
}

Compile error:

demo/GenericsHell.java:75: error: method useList in class GenericsHell cannot be applied to given types;
        useList(list);
        ^
  required: List<L>
  found: List<List<? extends Number>>
  reason: inference variable L has incompatible bounds
    equality constraints: List<? extends Number>
    upper bounds: List<CAP#1>
  where L,T are type-variables:
    L extends List<T> declared in method <L,T>useList(List<L>)
    T extends Number declared in method <L,T>useList(List<L>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number
1 error

In my real code list is being generated by a complex method that can return several types of "List" (actually a custom generic class). Is there another way to parameterize the useList function such that it will accept this in a type-safe way?


Edit 1

I am trying to reduce a large module of code into a concise, coherent question. From the answers I can see that the above code over-simplifies the precise problem I'm having. I'll try to restate with a more complex example, while better specifying my constraints.

First off, I'm not actually using List, but rather a more complex class with a double parameterization. I can't think of any standard classes that do this, so I'll define one for use in the example:

static class NumberLists<L extends List<T>,T extends Number> extends ArrayList<L> {}

I'll avoid reusing a type more than once, so it's easier to keep the levels straight. The useList() method really does need a double parameterization because it uses both types internally:

static <L extends List<T>,T extends Number>
Set<NumberLists<L,T>> useList(L list) {
    NumberLists<L,T> nl = new NumberLists<L, T>();
    nl.add(list);
    return Collections.singleton(nl);
}

This framework works great as long as you have a concrete class:

    // Everything works with a concrete class
    List<Integer> intList = new ArrayList<Integer>();

    // Use with parametric functions
    Set<NumberLists<List<Integer>,Integer>> concreteSet = useList(intList);
    // Access & use elements
    NumberLists<List<Integer>,Integer> concreteNL = concreteSet.iterator().next();
    concreteNL.add(intList);

The problem is that I have an input list that can be several different types:

static List<? extends Number> makeList() {
    if(Math.random()<.5) {
        return new ArrayList<Integer>();
    } else {
        return new ArrayList<Double>();
    }
}

List<? extends Number> numList = makeList();

This breaks many of the patterns that were possible above.

    // What is the type here? This is obviously an error
    Set<NumberLists<? extends List<? extends Number>>,? extends Number> paramSet = useList(numList);
    // Type should ideally be compatible with numList still
    NumberLists<?,?> paraNL1 = paramSet.iterator().next();
    // Captures don't match
    paraNL1.add(numList);

So the problem is that I can't know the concrete class of numList at compile time. And it doesn't feel like I'm doing anything that's actually type-unsafe, since I know that the type of numList has to match the type returned by useList and so on.

I do have control over the type signatures of most parts of the code. However, I would prefer the following:

  • It should work nicely and in a type-safe manner with concrete classes (e.g. intList)
  • It will also receive input whose type can't be known specifically until runtime
  • If casts or other unchecked operations are performed, they should happen near the construction of the input. Following operations should be checked.

For a complete (but non-compiling) java file, see https://gist.github.com/sbliven/f7babb729e0b1bee8d2dabe5ee979431. For the reason I'm interested in this question, see https://github.com/biojava/biojava/issues/354.

Quantum7
  • 3,165
  • 3
  • 34
  • 45
  • 1
    It looks to me like your list contains a list, and `useList()` expects the list to contain `Number`, not `List`. – markspace Nov 14 '16 at 22:38
  • @markspace In the top code block, I was hoping to get L to bind List extends Number>. I think it's the right number of levels. – Quantum7 Nov 15 '16 at 09:06
  • I've updated the question and avoided using nested lists. Hopefully that makes the type signatures less confusing. – Quantum7 Nov 15 '16 at 10:43

2 Answers2

2

The trouble is that the compiler wants to assign a concrete type to T. In your declaration T would need to become ? extends Number as per declaration of list, which is not concrete.

You can change useList to one of the following declarations and the compiler will be happy:

static <L extends List<? extends Number>> void useList(List<L> list) {
}

(no T, no problem) or

static <L extends List<? extends T>, T extends Number> void useList(List<L> list) {
}

(here T will become Number).

If you can replace ? extends Number in your variable-declaration, everything will be fine, too:

public static <T extends Number> void main(String[] args) {
    List<List<T>> list = null;
    useList(list);
}
TheConstructor
  • 4,285
  • 1
  • 31
  • 52
  • I think that this is a step towards what I was looking for, but none of these options seem suitable for me. I have edited my original question to more clearly explain my constraints. – Quantum7 Nov 15 '16 at 10:42
2

For the first compilation issue, your problem comes from the fact that you try to assign it to some variable, whose type does not match.

The simplest approach is to start from the useList() call to make sure that this simple code compiles:

List<? extends Number> numList = makeList();
useList(numList);

If you check this code, it compiles already.

Now ask your IDE to assign the result to a local variable. The IDE will compute the type of the expression for you to declare the variable, and voilà:

Set<? extends NumberLists<? extends List<? extends Number>, ? extends Number>> paramSet =
    useList(numList);

(If possible, get rid of the L type on NumbersList, that will simplify things a lot)

For the paraNL1.add(numList); call, it cannot work because of the wildcard: the compiler has no way to check that paraNL1 accepts your unknown type of list.

Even if you fix the declaration by following the same process:

NumberLists<? extends List<? extends Number>, ? extends Number> paraNL1 =
    paramSet.iterator().next();

paraNL1.add(numList); // will still not compile

You see that you are trying to use paraNL1 as a consumer (it consumes numList), and PECS tells you that you must declare it with super for that to work.

Indeed, the compiler knows that there must exist some type L extends List<? extends Number> for your NumberLists, but it does not know it and has no way to check that it matches the type of numList. L could be for example LinkedList<Float> while numList could be ArrayList<Integer>.

Edit: to make it work, you might be able to use a wilcard capture helper method like this:

private static <T extends Number, L extends List<T>> void helper(L numList) {
    Set<NumberLists<L, T>> paramSet = useList(numList);
    NumberLists<L, T> paraNL1 = paramSet.iterator().next();
    paraNL1.add(numList);
}

(this even allows you to get rid of the ? extends NumerLists in the Set declaration)

and call it with

List<? extends Number> numList = makeList();
helper(numList);
Community
  • 1
  • 1
Didier L
  • 18,905
  • 10
  • 61
  • 103
  • I guess I need a better IDE–Eclipse Luna suggested `Set>`. So is there some way I can redefine the classes using super so that this code will work? I'm not sure if that's possible in my real code, but it might give me some ideas. – Quantum7 Nov 15 '16 at 14:02
  • Luna is more than 2 years old now. Type inference has changed a lot with Java 8, and thus the support has greatly improved in the IDE's. The simplest to make things compile would be to avoid using wildcards. Maybe use a [wildcard capture helper method](http://stackoverflow.com/questions/30763895/why-use-a-wild-card-capture-helper-method). – Didier L Nov 15 '16 at 14:23
  • @Quantum7 I added an example with a wildcard capture helper method – Didier L Nov 15 '16 at 14:27
  • Wildcard capture is the technique I was missing. Thanks! – Quantum7 Nov 15 '16 at 16:21