通用列表的地图不能很好地转换

Set*_*son 1 java generics

我只想举个例子,而不是用语言来尝试:

我有动物类,以及延伸动物的狗,鱼,猫等.

我有三种不同的方法,它们会返回Map<String,List<Dog>>, Map<String,List<Fish>>, Map<String,List<Cat>>.我们将调用这些getDogMap,getCatMap和getFishMap.

我正在编写一个通用方法,根据各种参数调用这些方法之一.这是我期望被允许做的事情:

public void <A extends Animal> doSomething(){

    Map<String,List<A>> someMap;

    if(someCondition){
        someMap = getDogMap();
    }else if(anotherCondition){
        someMap = getFishMap();
    }else{
        someMap = getCatMap():
    }
}
Run Code Online (Sandbox Code Playgroud)

或至少,与铸造ala someMap = (Map<String,List<Dog>>) getDogMap();

但是,这不起作用.Eclipse告诉我"Type mismatch: cannot convert from Map<String,List<Dog>> to Map<String,List<A>>"如果我试图强制演员,它会告诉我"Cannot cast from Map<STring,List<Dog>> to Map<String,List<A>>".

我究竟做错了什么?

mil*_*ose 5

public void <A extends Animal>并不意味着" A是任何延伸的类型Animal",它意味着" A是一种特定的延伸类型Animal".您需要使用以下声明:

public void doSomething() {
    Map<String, ? extends List<? extends Animal>>  someMap;
    // ...
}
Run Code Online (Sandbox Code Playgroud)

构造? extends Animal是你如何表达"任何延伸的类型Animal".

您必须使用该声明的原因是,与直觉相反,泛型类型之间的子类型关系的工作方式与它们在常规类型之间的工作方式不完全一致.例如,List<Dog>是一个子类型Collection<Dog>.这是不是一个亚型List<Animal>,或Collection<Animal>等等.为什么这是不允许被称为原因堆污染,在还解释安格莉卡朗格的常见问题.List<Dog>但是,它是一个子类型List<? extends Animal>.类型变量List<? extends Animal>可能已分配a List<Dog>,或a List<Cat>或a List<Animal>.重要的是,编译器不知道它是哪一个,只是它是其中之一.

就像List<Dog>没有的一个亚型List<Animal>,类似于它认为Map<String, List<Dog>>不是一个亚型Map<String, List<? extends Animal>>.

证明为什么泛型以这种方式工作的最好方法是通过矛盾来证明; 也就是说,显示导致错误的(损坏的)代码示例是"直观地"工作的泛型.因此,如果List<Dog>是子类型List<Animal>,则以下代码有效:

List<Dog> dogs = new ArrayList<Dog>();
List<Animal> animals = dogs; // unsafe cast

// this operation violates type safety
animals.add(new Cat());

// would assign a Cat to a variable of type Dog without a compile error!
Dog dog = animals.get(0);
Run Code Online (Sandbox Code Playgroud)

同样,对于你Map:

Map<String, List<Dog>> dogses = new HashMap<String, List<Dog>>();
Map<String, List<? extends Animal>> animalses = dogses; // unsafe cast

List<Cat> cats = new ArrayList();
cats.put(new Cat());
animalses.put("cats", cats);

List<Dog> dogs = dogses.get("cats");
Dog dog = dogs.get(0); // uh-oh
Run Code Online (Sandbox Code Playgroud)

  • 一条经验法则:如果你的方法有一个既不在参数类型中使用的类型参数,也不在返回类型中,你可能会以错误的方式处理事情.方法类型参数仅用于*表示参数/ retval类型之间/之间的约束,它们不会有意义地影响方法体中的局部变量. (3认同)