gpt4 book ai didi

java - Java HashMap使用通配符嵌套泛型

转载 作者:搜寻专家 更新时间:2023-11-01 01:13:10 25 4
gpt4 key购买 nike

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

HashMap<String, Hashmap<String, HashSet<? extends AttackCard>>> superMap
AttackCard具有子类,例如: MageAssassinFighter。 superMap中的每个HashMap将仅具有包含 AttackCard的单个子类的HashSet。

当我尝试放一个
HashMap<String, HashSet<Assassin>>

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

以下是发生错误的代码:
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 get-method的一个片段:
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)

最佳答案

如果处理不当,多级通配符有时可能会有些棘手。您首先应该学习如何读取多级通配符。然后,您将需要学习解释多级通配符中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*

前2个非常清楚。

看看第三个。您将如何解释该声明?试想一下,该列表中可以包含什么类型的元素。所有可捕获转换为 List<? extends Number>的元素都可以放在外部列表中:
  • List<Number>-是
  • List<Integer>-是
  • List<Double>-是
  • List<String>-

  • 引用:
  • JLS §5.1.10 - Capture Conversion
  • Java Generics FAQs - Angelika Langer
  • Wildcard Capture
  • IBM Developer Works Article - Understanding Wildcard Captures

  • 鉴于list的第3个实例化可以容纳上述元素类型,因此将引用分配给这样的列表将是错误的:
    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>();

    引用:
  • What do multi-level wildcards mean?
  • Difference between a Collection<Pair<String,Object>> , a Collection<Pair<String,?>> and a Collection<? extends Pair<String,?>> ?


  • 与单级通配符有关:

    现在,这可能在您的脑海中勾勒出清晰的画面,这与泛型的不变性有关。 List<Number>不是 List<Double>,尽管 NumberDouble的父类(super class)。同样,即使 List<List<? extends Number>>List<List<Integer>>的父类(super class),但 List<? extends Number>也不是 List<Integer>

    涉及到具体问题:

    您已将 map 声明为:
    HashMap<String, Hashmap<String, HashSet<? extends AttackCard>>> superMap;

    请注意,该声明中有 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

    您只能将 HashMap<String, HashSet<? extends AttackCard>>分配给 map,因此只能将该类型的 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<>();

    现在,您将可以将 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>>();

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

    奖励积分:

    解决当前问题之后,您还应该考虑将所有具体的类类型引用更改为它们各自的 super 接口(interface)。您应该将 superMap的声明更改为:
    Map<String, Map<String, ? extends Set<? extends AttackCard>>> superMap;

    这样您就可以将 HashMapTreeMapLinkedHashMap任意类型分配给 superMap。另外,您将能够添加 HashMapTreeMap作为 superMap的值。了解 Liskov Substitution Principle的用法非常重要。

    关于java - Java HashMap使用通配符嵌套泛型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19219635/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com