Java HashMap使用通配符嵌套generics

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

HashMap<String, Hashmap<String, HashSet>> superMap 

AttackCard有子类,如: MageAssassinFighter 。 superMap中的每个HashMap只会包含一个包含AttackCard单个子类的AttackCard

当我尝试放一个

 HashMap<String, HashSet> 

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

下面是发生错误的代码:

 public class CardPool { private HashMap<String, HashMap<String, HashSet>> attackPool = new HashMap(); private ArrayList 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方法的片段:

 private HashMap<String, HashSet> pool = new HashMap(); public HashMap<String, HashSet> get() { return pool; } 

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

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

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

解释多级通配符:

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

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

前两个很清楚。

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

  • List – 是的
  • List – 是的
  • List – 是的
  • ListNO

参考文献:

  • JLS§5.1.10 – 捕获转换
  • Java Generics常见问题解答 – Angelika Langer
    • 通配符捕获
  • IBM Developer Works文章 – 了解通配符捕获

鉴于list的第3个实例可以保存上面提到的元素类型,将引用分配给这样的列表是错误的:

 List> list = new ArrayList>(); // Wrong 

上面的赋值不起作用,否则你可能会这样做:

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

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

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

 list4 = new ArrayList(); list4 = new ArrayList(); 

参考文献:

  • 多级通配符是什么意思?
  • Collection>Collection>Collection>之间的区别 Collection>

与单级通配符有关:

现在,这可能会在你的脑海中形成一幅清晰的画面,这与仿制品的不变性有关。 List不是List ,尽管NumberDouble超类。 同样, List> List>不是List>即使List ListList的超类。

来到具体问题:

您已将地图声明为:

 HashMap>> superMap; 

请注意,该声明中有3级嵌套 小心点 它类似于List>> List>> ,这与List> List>

现在你可以添加到superMap所有元素类型是superMap ? 当然,你不能将一个HashMap>superMap 。 为什么? 因为我们不能做这样的事情:

 HashMap> map = new HashMap>(); // This isn't valid 

你只能分配一个HashMap> HashMap>map ,因此只将该类型的地图作为值放在superMap

选项1:

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

 private HashMap> pool = new HashMap<>(); public HashMap> get() { return pool; } 

……一切都会好起来的。

选项2:

另一个选择是将superMap的声明superMap为:

 private HashMap>> superMap = new HashMap<>(); 

现在,您可以将HashMap>放到superMap 。 怎么样? 想一想。 HashMap>可以捕获转换为HashMap> HashMap> HashMap> 。 对? 因此,内部地图的以下分配是有效的:

 HashMap> map = new HashMap>(); 

因此,您可以在上面声明的superMap放置HashMap> 。 那么你在Assassin课上的原始方法就可以了。


奖励点:

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

 Map>> superMap; 

这样您就可以将任何类型的HashMapTreeMapLinkedHashMap分配给superMap 。 此外,您还可以将HashMapTreeMap添加为superMap值。 理解Liskov替代原则的用法非常重要。

不要使用HashSet HashSet ,只需在所有声明中使用HashSet – superMap和所有添加的集合。

您仍然可以在Set存储AttackCard子类。


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

 Map>> superMap 

见Liskov替代原则

可能是协方差问题,你需要更换吗? extends ? super ? super

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