使用上限通配符

时间:2018-04-12 09:16:42

标签: java generics inheritance wildcard

我真的很困惑上边界类型在Java泛型中是如何工作的。

假设我有

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

然后我有一个带参数的方法如下

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

调用该方法,如

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

以编译错误结束

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>>>>

我已经从Oracle网站上阅读了关于Java泛型的文档,试图谷歌它,但必须有一些我完全被误解的东西。

3 个答案:

答案 0 :(得分:3)

这个问题可以缩短为什么

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

不能接受像

这样的论点

IModel<List<A>> model

说明

List<A>List<? extends I>的子类型,所以没关系:

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

但是,它不会使IModel<List<A>>成为IModel<List<? extends I>>的子类型,就像IModel<Dog> is not a subtype of IModel<Animal>一样,所以您发布的代码无法编译。

解决方案

您可以将其更改为:

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

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

让它编译。<​​/ p>

答案 1 :(得分:2)

首先,我想知道你(一个人)将代码整理成这种形式会付出多少努力:

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)
    {

    }
}

而不是让数百人(想要帮助你)自己做这件事,以便拥有他们可以编译并在他们的IDE中查看的东西。我的意思是, 很难。

话虽如此:从技术上讲,你在这里错过了几个extends条款。编译好了:

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)
    {

    }
}

但你应该

像那样实现它。这是模糊不清的。无论这个dataModel参数是什么,你应该考虑为它创建一个合适的数据结构,而不是传递如此混乱的深嵌套通用映射。

原始版本未编译的原因已在其他答案中提及。通过使用更简单的方法调用显示示例,可以使其更清晰。考虑这个例子:

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)
    {

    }
}

exampleA方法无法接受给定列表,而exampleB方法可以接受它。

详细信息在Angelika Langer的generics FAQ的Which super-subtype relationships exist among instantiations of generic types?中得到了很好的解释。

直观地说,关键点是类型List<A>List<? extends I>的子类型。但是,让该方法仅接受List<List<? extends I>>不允许您传入其元素为List<? extends I>的子类型的列表。要接受子类型,您必须使用? extends

(甚至可以进一步简化:当方法接受List<Number>时,您无法传入List<Integer>。但这不会使List<A>成为子类型List<? extends I>清楚这里)

答案 2 :(得分:0)

有一个方法method1(Map<I> aMap>)而A是一个实现我的类,我不允许你用Map<A>调用该方法,这是有原因的。

有方法:

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

想象一下这段代码:

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

这不应该起作用,因为您为需要泛型类型的方法提供了更具体的类型。现在假设foo2执行以下操作:

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

在这里,您将尝试将IModel设置为有效的IModel - 因为B扩展了I,但是如果您能够使用IModel调用该方法则无法正常工作

创建您的模型,如:

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

并在相应的地图和列表中添加有效的A,B和C类型的对象,然后调用函数foo(model)