7

我正在尝试制作包含自定义类的不同子类的哈希集的哈希映射值的哈希映射,如下所示:

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

AttackCard有子类,例如:Mage, Assassin, Fighter. superMap 中的每个 HashMap 都只会有包含AttackCard.

当我尝试放一个

HashMap<String, HashSet<Assassin>>

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

以下是发生错误的代码:

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 获取方法的片段:

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

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

我想评论一下,我可以通过使所有 AttackCardPool(例如 AssassinPool)返回并包含 AttackCard 的 HashSet 而不是它们各自的子类来轻松解决我的问题并拥有一个出色的工作程序。我试图理解这个编译错误,但是:)

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

3 回答 3

15

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

解释多级通配符:

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

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*

前2个很清楚。

看看第三个。您如何解释该声明?试想一下,该列表中可以包含哪些类型的元素。所有可捕获转换为 的元素都List<? extends Number>可以进入外部列表:

  • List<Number>- 是的
  • List<Integer>- 是的
  • List<Double>- 是的
  • List<String>-没有

参考:

鉴于 list 的第三个实例可以包含上述类型的元素,将引用分配给这样的列表是错误的:

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次实例化。该列表表示所有具有类型参数的实例化家族,这些List类型参数是 的子类List<Number>。因此,以下分配对此类列表有效:

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

参考:


与单级通配符有关:

现在这可能会在您的脑海中勾勒出一幅清晰的画面,这与泛型的不变性有关。AList<Number>不是List<Double>,虽然Number是 的超类Double。类似地,aList<List<? extends Number>>不是 a ,List<List<Integer>>即使aList<? extends Number>是 的超类List<Integer>

来到具体问题:

您已将地图声明为:

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

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

现在你可以添加什么元素类型superMap?当然,您不能将 a 添加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>>to 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<>();

现在,您可以将 aHashMap<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>>在上面声明的中放入 a superMap。然后你在Assassin课堂上的原始方法就可以正常工作了。


奖励积分:

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

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

这样您就可以将HashMaporTreeMapLinkedHashMap, anytype分配给superMap. 此外,您还可以添加HashMapTreeMap作为 的值superMap。理解Liskov Substitution Principle的用法非常重要。

于 2013-10-07T08:49:14.643 回答
2

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

您仍然可以将子类存储AttackCardSet<AttackCard>.


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

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

参见Liskov 替换原则

于 2013-10-07T08:12:27.173 回答
0

可能是协方差问题,您需要替换吗?extends? super.

请参阅什么是 PECS(生产者扩展消费者超级)?

于 2013-10-07T08:03:12.223 回答