2

I'm really confused of how upper bounded types work in Java generics.

Let's say I have

interface IModel<T>
interface I
class A implements I
class B implements I
class C implements I

then I have a method with parameter as follows

foo(IModel<Map<? extends I, Map<? extends I, List<? extends I>>>> dataModel)

calling that method like

IModel<Map<A, Map<B, List<C>>>> model = ...
foo(model)

ends with compilation error

Error:(112, 49) java: incompatible types: IModel<java.util.Map<A,java.util.Map<B,java.util.List<C>>>> cannot be converted to IModel<java.util.Map<? extends I,java.util.Map<? extends I,java.util.List<? extends I>>>>

I have read docs about Java generics from the Oracle web, trying to google it, but there must be something I totally misunderstood.

xingbin
  • 27,410
  • 9
  • 53
  • 103
Jan Krakora
  • 2,500
  • 3
  • 25
  • 52

3 Answers3

3

This question can be shorted as why

foo(IModel<List<? extends I>> dataModel)

can not accept argument like

IModel<List<A>> model

Explanation

List<A> is a subtype of List<? extends I>, so it is ok:

public void bar(List<? extends I> list);
List<A> listA;
bar(listA);

But, it does not make IModel<List<A>> a subtype of IModel<List<? extends I>>, just like IModel<Dog> is not a subtype of IModel<Animal>, so the code you posted can not be compiled.

Solution

You can change it to:

foo(IModel<? extends Map<? extends I, ? extends Map<? extends I, ? extends List<? extends I>>>> dataModel)

or

<FIRST extends I, SECOND extends I, THIRD extends I> void foo(IModel<Map<FIRST, Map<SECOND, List<THIRD>>>> dataModel)

to make it compile.

xingbin
  • 27,410
  • 9
  • 53
  • 103
2

First of all, I wonder how much effort it would have been for you (one person) to sort out the code to be in this form:

import java.util.List;
import java.util.Map;

interface IModel<T> {}
interface I {}
class A implements I {}
class B implements I {}
class C implements I {}

public class UpperBounds
{
    public static void main(String[] args)
    {
        IModel<Map<A, Map<B, List<C>>>> model = null;
        foo(model);
    }
    
    static void foo(IModel<Map<? extends I, Map<? extends I, List<? extends I>>>> dataModel)
    {
        
    }
}

instead of letting hundreds of people (who want to help you) do this on their own, in order to have something that they can compile and have a look at in their IDE. I mean, it's not that hard.


That being said: Technically, you're missing a few more extends clauses here. This compiles fine:

import java.util.List;
import java.util.Map;

interface IModel<T> {}
interface I {}
class A implements I {}
class B implements I {}
class C implements I {}

public class UpperBounds
{
    public static void main(String[] args)
    {
        IModel<Map<A, Map<B, List<C>>>> model = null;
        foo(model);
    }
    
    static void foo(IModel<? extends Map<? extends I, ? extends Map<? extends I, ? extends List<? extends I>>>> dataModel)
    {
        
    }
}

But you should

not

implement it like that. That's obscure. Whatever this dataModel parameter is, you should consider creating a proper data structure for that, instead of passing along such a mess of deeply nested generic maps.


The reason of why the original version did not compile was already mentioned in other answers. And it can be made clearer by showing an example using a much simpler method call. Consider this example:

interface IModel<T> {}
interface I {}
class A implements I {}
class B implements I {}
class C implements I {}

public class UpperBounds
{
    public static void main(String[] args)
    {
        List<List<A>> lists = null;
        exampleA(lists); // Error
        exampleB(lists); // Works!
    }
    
    static void exampleA(List<List<? extends I>> lists)
    {
        
    }
    static void exampleB(List<? extends List<? extends I>> lists)
    {
        
    }
}

The exampleA method cannot accept the given list, whereas the exampleB method can accept it.

The details are explained nicely in Which super-subtype relationships exist among instantiations of generic types? of the generics FAQ by Angelika Langer.

Intuitively, the key point is that the type List<A> is a subtype of List<? extends I>. But letting the method accept only a List<List<? extends I>> does not allow you to pass in a list whose elements are subtypes of List<? extends I>. In order to accept subtypes, you have to use ? extends.

(This could even be simplified further: When a method accepts a List<Number>, then you cannot pass in a List<Integer>. But this would not make the point of List<A> being a subtype of List<? extends I> clear here)

Community
  • 1
  • 1
Marco13
  • 53,703
  • 9
  • 80
  • 159
  • You are right. I was thinking this kind of problem is rather philosophical, so it would be enough to provide just a pseudocode. I'll do it better next time. – Jan Krakora Apr 12 '18 at 11:52
0

Having a method method1(Map<I> aMap>) and A being a class implementing I doesn't allow you to call the method with a Map<A> and that's for a reason.

Having the method:

public static void foo2(IModel<I> dataModel) {
        System.out.println("Fooing 2");
    }

Imagine this code:

IModel<A> simpleModel = new IModel<A>() {};
foo2(simpleModel);

This shouldn't work because you supply a more specific type to a method that requires a generic type. Now imagine foo2 does the following:

   public static void foo2(IModel<I> dataModel) {
        dataModel = new IModel<B>() {};
        System.out.println("Fooing 2 after we change the instance");
    }

Here you will try to set IModel to IModel which is valid - because B extends I, but if you were able to call that method with IModel it wouldn't work

Create your model like:

IModel<Map<I, Map<I, List<I>>>> model = ...

and in the corresponding maps and lists add objects of type A, B and C which will be valid and then call the function foo(model)

Veselin Davidov
  • 7,031
  • 1
  • 15
  • 23