Java HashMap使用通配符嵌套泛型

use*_*804 4 java generics hashmap

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

HashMap<String, Hashmap<String, HashSet<? extends AttackCard>>> superMap
Run Code Online (Sandbox Code Playgroud)

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

当我尝试放一个

HashMap<String, HashSet<Assassin>>
Run Code Online (Sandbox Code Playgroud)

进入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()); 
}
Run Code Online (Sandbox Code Playgroud)

这里有一个AssassinPool get方法的片段:

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

    public HashMap<String, HashSet<Assassin>> get() {
        return pool;
    }
Run Code Online (Sandbox Code Playgroud)

我想评论一下,我可以很容易地解决我的问题并通过使所有的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)
Run Code Online (Sandbox Code Playgroud)

Roh*_*ain 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*
Run Code Online (Sandbox Code Playgroud)

前两个很清楚.

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

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

参考文献:

鉴于3 列表可以容纳上述类型的元素的实例,这将是错误的指定参考像这样的列表:

List<List<? extends Number>> list = new ArrayList<List<Integer>>();  // Wrong
Run Code Online (Sandbox Code Playgroud)

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

list.add(new ArrayList<Float>());  // You can add an `ArrayList<Float>` right?
Run Code Online (Sandbox Code Playgroud)

所以发生了什么事?你刚刚添加了ArrayList<Float>一个集合,它应该List<Integer>只保留一个集合.那肯定会在运行时给你带来麻烦.这就是为什么它不被允许,编译器仅在编译时阻止它.

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

list4 = new ArrayList<Integer>(); 
list4 = new ArrayList<Double>(); 
Run Code Online (Sandbox Code Playgroud)

参考文献:


与单级通配符有关:

现在,这可能会在你的脑海中形成一幅清晰的画面,这与仿制品的不变性有关.A List<Number>不是List<Double>,虽然Number是超类Double.同样地,a List<List<? extends Number>>不是List<List<Integer>>偶数,尽管它List<? extends Number>是超类List<Integer>.

来到具体问题:

您已将地图声明为:

HashMap<String, Hashmap<String, HashSet<? extends AttackCard>>> superMap;
Run Code Online (Sandbox Code Playgroud)

请注意,该声明中有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
Run Code Online (Sandbox Code Playgroud)

您只能分配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;
}
Run Code Online (Sandbox Code Playgroud)

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

选项2:

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

private HashMap<String, HashMap<String, ? extends HashSet<? extends AttackCard>>> superMap = new HashMap<>();
Run Code Online (Sandbox Code Playgroud)

现在,你就可以把一个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>>();
Run Code Online (Sandbox Code Playgroud)

因此你可以HashMap<String, HashSet<Assassin>>在上面声明一个superMap.然后你在Assassin课堂上的原始方法可以正常工作.


奖励点:

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

Map<String, Map<String, ? extends Set<? extends AttackCard>>> superMap;
Run Code Online (Sandbox Code Playgroud)

所以,你可以指定任意HashMapTreeMap或者LinkedHashMap,anytype类型的superMap.此外,您还可以添加HashMapTreeMap作为值的值superMap.了解Liskov Substitution Principle的用法非常重要.