为什么这个简单的Java泛型函数不能编译?

时间:2010-09-05 01:45:04

标签: java generics

f1编译时,非常相似的f2不会,我只是无法解释原因。 (在Intellij 9和Eclipse 3.6上测试)

我真的以为我已经完成了这样的问题。

import java.util.*;
public class Demo {

    public  List<? extends Set<Integer>> f1(){
        final List<HashSet<Integer>> list = null;
        return list;         
    } 

    public  List<List<? extends Set<Integer>>> f2(){
        final List<List<HashSet<Integer>>> list = null;
        return list;         
    } 

}

3 个答案:

答案 0 :(得分:11)

List<List<HashSet<Integer>>>无法分配给List<List<? extends Set<Integer>>>,原因相同List<HashSet<Integer>>无法分配给List<Set<Integer>>

你可以通过更改它来编译它:

public  List<List<? extends Set<Integer>>> f2(){

进入这个:

public  List<? extends List<? extends Set<Integer>>> f2(){

你的代码没有编译的原因,以及为什么我给出的另一个例子(即:“List<HashSet<Integer>>无法分配给List<Set<Integer>>”)是Java generics are not covariant

规范示例是,即使Circle延伸ShapeList<Circle> 也不会延伸List<Shape>。如果确实如此,则List<Circle>需要add(Shape)方法接受Square个对象,但显然您不希望能够将Square个对象添加到List<Circle>

当您使用通配符时,您将获得一个切掉某些方法的类型。 List<? extends Shape>保留返回E的方法,但它没有将E作为参数的任何方法。这意味着您仍然使用E get(int)方法,但add(E)已消失。 List<? extends Shape>是超级类型List<Shape>以及List<Circle>List<? extends Circle>等。(? super通配符以另一种方式切片:的方法返回类型参数的值)

你的例子更复杂,因为它有嵌套的类型参数,但归结为同样的事情:

  • List<HashSet<Integer>>List<? extends Set<Integer>>
  • 的子类型
  • 因为泛型不是协变的,所以将这两种类型包装在泛型类型(如List<...>)中会产生一对不再具有子/超类型关系的类型。也就是说,List<List<HashSet<Integer>>> List<List<? extends Set<Integer>>>的子类型
  • 如果不是用List<...>换行,而是用List<? extends ...>换行,最终会保留原始关系。 (这只是一个经验法则,但它可能涵盖了80%你想要使用通配符的情况。)

请注意,trashgod和BalusC都是正确的,因为你可能不希望返回这种奇怪的类型。 List<List<Set<Integer>>>将是一种更常用的返回类型。只要你始终使用集合接口而不是具体的集合类作为类型参数,这应该可以正常工作。例如:您无法将List<ImmutableSet<Integer>>分配给List<Set<Integer>>,但您可以将ImmutableSet<Integer>个实例放入List<Set<Integer>>,所以永远不要说List<ImmutableSet<Integer>>,说{ {1}}。

答案 1 :(得分:6)

不要使用通配符类型作为返回类型。它不会为用户提供额外的灵活性,而是迫使他们在客户端代码中使用通配符类型。” - Joshua Bloch,{{3} },第5章,第28项。

答案 2 :(得分:2)

这太长了,不适合评论。我只想说,以这种方式宣布它是没有意义的。您也可以执行以下操作:

public List<List<Set<Integer>>> f2() {
    List<List<Set<Integer>>> list = new ArrayList<List<Set<Integer>>>();
    List<Set<Integer>> nestedList = new ArrayList<Set<Integer>>();
    list.add(nestedList);
    Set<Integer> set = new HashSet<Integer>();
    nestedList.add(set);
    return list;
}

效果很好。我认为没有必要在这里使用? extends SomeInterface


更新:根据评论,您最初想解决以下问题:

public List<Map<Integer, Set<Integer>>> getOutcomes() {
    Map<HashSet<Integer>, Integer> map = new HashMap<HashSet<Integer>, Integer>();
    List<Map<Integer, Set<Integer>>> outcomes = new ArrayList<Map<Integer, Set<Integer>>>();
    for (Map.Entry<HashSet<Integer>, Integer> entry : map.entrySet()) {
        outcomes.add(asMap(entry.getValue(), entry.getKey())); 
        // add() gives compiler error: The method add(Map<Integer,Set<Integer>>)
        // in the type List<Map<Integer,Set<Integer>>> is not applicable for 
        // the arguments (Map<Integer,HashSet<Integer>>)
    }
    return outcomes;
}

public <K, V> Map<K, V> asMap(K k, V v) {
    Map<K, V> result = new HashMap<K, V>();
    result.put(k, v);
    return result;
}

这可以通过声明接口(在这种情况下为Set)而不是实现(在这种情况下为HashSet)作为泛型类型来解决。所以:

public List<Map<Integer, Set<Integer>>> getOutcomes() {
    Map<Set<Integer>, Integer> map = new HashMap<Set<Integer>, Integer>();
    List<Map<Integer, Set<Integer>>> outcomes = new ArrayList<Map<Integer, Set<Integer>>>();
    for (Map.Entry<Set<Integer>, Integer> entry : map.entrySet()) {
        outcomes.add(asMap(entry.getValue(), entry.getKey()));
        // add() now compiles fine.
    }
    return outcomes;
}

在未来的问题中,尝试询问如何解决特定问题,而不是如何实现特定解决方案(毕竟这可能不是正确的解决方案)。