Java HashMap使用通配符嵌套泛型

时间:2013-10-07 07:59:24

标签: java generics hashmap

我正在尝试制作一个hashmap值的hashmap,其中包含自定义类的不同子类的hashsets,如下所示:

HashMap<String, Hashmap<String, HashSet<? extends AttackCard>>> superMap

AttackCard包含以下子类:MageAssassinFighter。 superMap中的每个HashMap只有包含AttackCard的单个子类的HashSets。

当我尝试放

HashMap<String, HashSet<Assassin>>

进入superMap,我得到一个编译器错误: comiler error

下面是发生错误的代码:

public class CardPool {

private HashMap<String, HashMap<String, HashSet<? extends AttackCard>>> attackPool =
    new HashMap<>();

private ArrayList<AuxiliaryCard> auxiliaryPool;

public CardPool() {
(line 24)this.attackPool.put("assassins", new AssassinPool().get());
/*  this.attackPool.put("fighters", new Fighter().getPool());
    this.attackPool.put("mages", new Mage().getPool());
    this.attackPool.put("marksmen", new Marksman().getPool());
    this.attackPool.put("supports", new Support().getPool());
    this.attackPool.put("tanks", new Tank().getPool());
*/  
    this.auxiliaryPool = new ArrayList<>(new AuxiliaryCard().getPool()); 
}

以下是AssassinPool get-method的片段:

private HashMap<String, HashSet<Assassin>> pool = new HashMap<>();

    public HashMap<String, HashSet<Assassin>> get() {
        return pool;
    }

我想评论一下,我可以很容易地解决我的问题并通过使所有的AttackCardPools(例如AssassinPool)返回并包含AttackCard的HashSets而不是它们各自的子类来完成一个非常好的工作程序。我试图理解这个编译错误,但是:)

compilation error at line 24: error: no suitable method found for `put(String, HashMap<String,HashSet<Assassin>>>` 
this.attackPool.put("assassins", new AssassinPool(). get()); 
method HashMap.putp.(String, HashMap<String,HashSet<? extends AttackCard>>>` is not applicable (actual argument `HashMap<String, HashSet<Assassin>>` cannot be converted to `HashMap<String, HashSet<? extends AttackCard>>` by method invocation conversion)

3 个答案:

答案 0 :(得分:11)

如果处理不当,多级通配符有时可能会有点棘手。您应该首先学习如何阅读多级通配符。然后,您需要学习解释多级通配符中extendssuper边界的含义。这些是在开始使用它们之前必须首先学习的重要概念,否则你很快就会发疯。

解释多级通配符:

**应自上而下读取多级通配符*。首先阅读最外面的类型。如果这又是一个参数化类型,那么深入了解该参数化类型的类型。理解具体参数化类型通配符参数化类型的含义对于理解如何使用它们起着关键作用。例如:

List<? extends Number> list;   // this is wildcard parameterized type
List<Number> list2;            // this is concrete parameterized type of non-generic type
List<List<? extends Number>> list3;  // this is *concrete paramterized type* of a *wildcard parameterized type*.
List<? extends List<Number>> list4;  // this is *wildcard parameterized type*

前两个很清楚。

看看第3个。你会如何解释这个宣言?试想一下,该列表中可以包含哪些类型的元素。所有可以转换为List<? extends Number>的元素都可以进入外部列表:

  • List<Number> - 是
  • List<Integer> - 是
  • List<Double> - 是
  • List<String> -

<强>参考文献:

鉴于列表的3 rd 实例化可以保存上述类型的元素,将引用分配给这样的列表是错误的:

List<List<? extends Number>> list = new ArrayList<List<Integer>>();  // Wrong

上述作业不起作用,否则你可能会这样做:

list.add(new ArrayList<Float>());  // You can add an `ArrayList<Float>` right?

那么,发生了什么?您刚刚将ArrayList<Float>添加到一个集合中,该集合应该仅包含List<Integer>。这肯定会在运行时给你带来麻烦。这就是为什么它不被允许,编译器仅在编译时阻止它。

但是,请考虑多级通配符的4 th 实例化。该列表表示List的所有实例化的族,其类型参数是List<Number>的子类。因此,以下分配对此类列表有效:

list4 = new ArrayList<Integer>(); 
list4 = new ArrayList<Double>(); 

<强>参考文献:


与单级通配符有关:

现在这可能会在你的脑海中形成一幅清晰的画面,这与仿制品的不变性有关。 List<Number>不是List<Double>,但NumberDouble的超类。同样,即使List<List<? extends Number>>List<List<Integer>>的超类,List<? extends Number>也不是List<Integer>

遇到具体问题:

您已将地图声明为:

HashMap<String, Hashmap<String, HashSet<? extends AttackCard>>> superMap;

请注意,该声明中有 3级嵌套小心。它与List<List<List<? extends Number>>>类似,与List<List<? extends Number>>不同。

现在您可以添加到superMap的所有元素类型?当然,您无法在HashMap<String, HashSet<Assassin>>中添加superMap。为什么?因为我们不能做这样的事情:

HashMap<String, HashSet<? extends AttackCard>> map = new HashMap<String, HashSet<Assassin>>();   // This isn't valid

您只能将HashMap<String, HashSet<? extends AttackCard>>分配给map,因此只会将该类型的地图作为值superMap

选项1:

因此,一个选项是将Assassin类(我猜它是)的代码的最后一部分修改为:

private HashMap<String, HashSet<? extends AttackCard>> pool = new HashMap<>();

public HashMap<String, HashSet<? extends AttackCard>> get() {
    return pool;
}

......一切都会好起来的。

选项2:

另一种选择是将superMap的声明更改为:

private HashMap<String, HashMap<String, ? extends HashSet<? extends AttackCard>>> superMap = new HashMap<>();

现在,您可以将HashMap<String, HashSet<Assassin>>添加到superMap。怎么样?想一想。 HashMap<String, HashSet<Assassin>>可以被捕获转换为HashMap<String, ? extends HashSet<? extends AttackCard>>。对?因此,内部地图的以下分配是有效的:

HashMap<String, ? extends HashSet<? extends AttackCard>> map = new HashMap<String, HashSet<Assassin>>();

因此,您可以在上面声明的HashMap<String, HashSet<Assassin>>中添加superMap。然后,Assassin类中的原始方法可以正常工作。


奖励积分:

解决当前问题后,还应考虑将所有具体类类型引用更改为各自的超级接口。您应该将superMap的声明更改为:

Map<String, Map<String, ? extends Set<? extends AttackCard>>> superMap;

这样您就可以将HashMapTreeMapLinkedHashMap,任意类型分配给superMap。此外,您还可以添加HashMapTreeMap作为superMap的值。了解Liskov Substitution Principle的用法非常重要。

答案 1 :(得分:2)

不要使用HashSet<? extends AttackCard>,只需在所有声明中使用HashSet<AttackCard> - superMap和所有要添加的集。

您仍然可以将AttackCard的子类存储在Set<AttackCard>


您应该使用 abstract 类型声明变量,而不是具体植入,即:

Map<String, Map<String, Set<? extends AttackCard>>> superMap

请参阅Liskov substitution principle

答案 2 :(得分:0)

可能是协方差问题,你需要更换吗? extends ? super。{/ p>

请参阅What is PECS (Producer Extends Consumer Super)?