Java HashMap使用通配符嵌套泛型 [英] Java HashMap nested generics with wildcards

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

问题描述

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

  HashMap< String,Hashmap< String,HashSet<?扩展AttackCard>>> superMap 

AttackCard 具有子类,如:法师刺客战士。 superMap中的每个HashMap都将只有一个HashSets,它包含一个 AttackCard 的单个子类。 当我尝试将

  HashMap< String,HashSet< Assassin>> 

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

  • Java Generics常见问题解答 - Angelika Langer


  • IBM开发人员工程文章 - 理解通配符捕获



  • 假设list的3 实例可以保存上述上述类型的元素,将引用赋值给像这样的列表是错误的:

      List< List<扩展Number>> list = new ArrayList< List< Integer>>(); //错误

    上述分配不应该起作用,否则你可能会这样做: p>

      list.add(new ArrayList< Float>()); //你可以添加一个`ArrayList< Float>'的权利? 

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



    但是,考虑多级通配符的第4个实例。该列表代表 List 的所有实例的族,其中的类型参数是 List< Number> 的子类。因此,以下分配对这些列表有效:

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

    参考文献:


    • 多级通配符是什么意思?

    • 集合< Pair< String,Object>> ,a 集合< Pair< String,?>>>之间的区别; 和一个集合<?延伸Pair< String,?>>






    关于单层通配符:



    现在这可能会让您的头脑清晰,回到泛型的不变性。虽然 Number List< Number> 不是 List< Double> c $ c>是 Double的超类。同样, List< List<即使 List <?>扩展Number>> 不是 List< List< Integer>> extends Number> List的一个超类< Integer>

    即将到来的具体问题:



    您已将您的地图声明为:

      HashMap< String,Hashmap< String,HashSet<?扩展AttackCard>>>北京超图; 

    请注意, 3级嵌套宣言。 小心。它类似于 List< List< List< ;?扩展Number>>> ,它不同于 List< List<扩展数字>>



    现在,您可以添加到 superMap ?当然,你不能在 superMap 中添加 HashMap< String,HashSet< Assassin>> 。为什么?因为我们不能这样做:

      HashMap< String,HashSet <?扩展AttackCard>> map = new HashMap< String,HashSet< Assassin>>(); //这是无效的

    您只能指定一个 HashMap< String ,HashSet <?将AttackCard>> 扩展到 map ,因此只将该类型的地图作为值放入 superMap

    选项1:

    所以,一种方法是修改最后一部分 Assassin class(我猜是这样):

     私有HashMap< String,HashSet <?扩展AttackCard>> pool = new HashMap<>(); 

    public HashMap< String,HashSet <?扩展AttackCard>> get(){
    返回池;
    }

    ...并且所有的都可以正常工作。



    选项2:
    $ b 另一个选项是更改 superMap to:

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

    现在,您可以将 HashMap< String,HashSet<刺客>> superMap 。怎么样?想想看。 HashMap< String,HashSet< Assassin>> 可捕获转换为 HashMap< String,?扩展HashSet <?扩展AttackCard>> 。对?因此,以下内部映射的赋值是有效的:

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

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






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

      Map< String,地图< String,?扩展Set <?扩展AttackCard>>>北京超图; 

    因此,您可以分配 HashMap TreeMap LinkedHashMap ,任何类型到 superMap 。此外,您可以添加一个 HashMap TreeMap 作为 superMap 。了解 Liskov替代原则的用法非常重要。


    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 has subclasses such as: Mage, Assassin, Fighter. Each HashMap in the superMap will only ever have HashSets containing a single subclass of AttackCard.

    When I try putting a

    HashMap<String, HashSet<Assassin>>
    

    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()); 
    }
    

    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;
        }
    

    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)
    

    解决方案

    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.

    Interpreting a multi-level wildcard:

    **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*
    

    First 2 are pretty clear.

    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<Number> - Yes
    • List<Integer> - Yes
    • List<Double> - Yes
    • List<String> - NO

    References:

    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?
    

    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.

    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>(); 
    

    References:


    Relating to single-level wildcard:

    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>.

    Coming to the concrete problem:

    You have declared your map as:

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

    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>>.

    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
    

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

    Option 1:

    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.

    Option 2:

    Another option is to change the declaration of superMap to:

    private HashMap<String, HashMap<String, ? extends HashSet<? extends AttackCard>>> superMap = new 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>>();
    

    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.


    Bonus Point:

    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;
    

    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天全站免登陆