带有通配符的 Java HashMap 嵌套泛型 [英] Java HashMap nested generics with wildcards

查看:21
本文介绍了带有通配符的 Java HashMap 嵌套泛型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to make a hashmap of hashmap values containing hashsets of different subclasses of a custom class, like so:

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

AttackCard 有子类,例如:MageAssassinFighter.superMap 中的每个 HashMap 都只会有包含 AttackCard 的单个子类的 HashSet.

AttackCard has subclasses such as: Mage, Assassin, Fighter. Each HashMap in the superMap will only ever have HashSets containing a single subclass of AttackCard.

当我尝试放置一个

HashMap<String, HashSet<Assassin>>

进入superMap,出现编译错误:

into superMap, I get a compiler error:

以下是发生错误的代码:

below is the code where the error occurs:

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

And here is a snippet of the AssassinPool get-method:

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

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

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

I'd like to comment that I could easily solve my problem and have a wonderfully working program by making all the AttackCardPools, such as AssassinPool, return and contain HashSets of AttackCard instead of their respective subclass. I'm trying to understand this compilation error, however :)

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)

推荐答案

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

Multi-level wildcards can be a bit tricky at times, when not dealt with properly. You should first learn how to read a multi-level wildcards. Then you would need to learn to interpret the meaning of extends and super bounds in multi-level wildcards. Those are important concepts that you must first learn before starting to use them, else you might very soon go mad.

解释多级通配符:

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

**Multi-level wildcards* should be read top-down. First read the outermost type. If that is yet again a paramaterized type, go deep inside the type of that parameterized type. The understanding of the meaning of concrete parameterized type and wildcard parameterized type plays a key role in understand how to use them. For example:

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*

前两个很清楚.

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

Take a look at the 3rd one. How would you interpret that declaration? Just think, what type of elements can go inside that list. All the elements that are capture-convertible to List<? extends Number>, can go inside the outer list:

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

参考:

  • JLS §5.1.10 - 捕获转换
  • Java 泛型常见问题解答 - Angelika Langer

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

    Given that the 3rd instantiation of list can hold the above mentioned type of element, it would be wrong to assign the reference to a list like this:

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

    上面的分配不应该工作,否则你可能会做这样的事情:

    The above assignment should not work, else you might then do something like this:

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

    所以,发生了什么?你刚刚添加了一个 ArrayList 到一个集合中,它应该只保存一个 List.那肯定会在运行时给您带来麻烦.这就是为什么它是不允许的,编译器只在编译时阻止它.

    So, what happened? You just added an ArrayList<Float> to a collection, which was supposed to hold a List<Integer> only. That will certainly give you trouble at runtime. That is why it's not allowed, and compiler prevents this at compile time only.

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

    However, consider the 4th instantiation of multi-level wildcard. That list represents a family of all instantiation of List with type parameters that are subclass of List<Number>. So, following assignments are valid for such lists:

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

    参考:

    关于单级通配符:

    现在这可能在您的脑海中清晰地描绘出来,这与泛型的不变性有关.List 不是 List,尽管 NumberDouble 的超类.类似地,一个 ListListList 的超类.

    Now this might be making a clear picture in your mind, which relates back to the invariance of generics. A List<Number> is not a List<Double>, although Number is superclass of Double. Similarly, a List<List<? extends Number>> is not a List<List<Integer>> even though the List<? extends Number> is a superclass of List<Integer>.

    进入具体问题:

    您已将地图声明为:

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

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

    Note that there is 3-level of nesting in that declaration. Be careful. It's similar to List<List<List<? extends Number>>>, which is different from List<List<? extends Number>>.

    现在您可以将哪些元素类型添加到 superMap 中?当然,您不能将 HashMap> 添加到 superMap 中.为什么?因为我们不能做这样的事情:

    Now what all element type you can add to the superMap? Surely, you can't add a HashMap<String, HashSet<Assassin>> into the superMap. Why? Because we can't do something like this:

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

    你只能分配一个HashMap> 扩展到 map,因此只将该类型的地图作为 superMap 中的值.

    You can only assign a HashMap<String, HashSet<? extends AttackCard>> to map and thus only put that type of map as value in superMap.

    选项 1:

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

    So, one option is to modify your last part of the code in Assassin class(I guess it is) to:

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

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

    ... and all will work fine.

    选项 2:

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

    Another option is to change the declaration of superMap to:

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

    现在,您可以将 HashMap> 放入 superMap.如何?想一想.HashMap> 可捕获转换为 HashMap>.对?因此内部映射的以下分配是有效的:

    Now, you would be able to put a HashMap<String, HashSet<Assassin>> to the superMap. How? Think of it. HashMap<String, HashSet<Assassin>> is capture-convertible to HashMap<String, ? extends HashSet<? extends AttackCard>>. Right? So the following assignment for the inner map is valid:

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

    因此你可以在上面声明的superMap中放置一个HashMap>.然后你在 Assassin 类中的原始方法就可以正常工作了.

    And hence you can put a HashMap<String, HashSet<Assassin>> in the above declared superMap. And then your original method in Assassin class would work fine.

    奖励积分:

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

    After solving the current issue, you should also consider to change all the concrete class type reference to their respective super interfaces. You should change the declaration of superMap to:

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

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

    So that you can assign either HashMap or TreeMap or LinkedHashMap, anytype to the superMap. Also, you would be able to add a HashMap or TreeMap as values of the superMap. It's really important to understand the usage of Liskov Substitution Principle.

    这篇关于带有通配符的 Java HashMap 嵌套泛型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆