带有通配符的 Java HashMap 嵌套泛型 [英] Java HashMap nested generics with wildcards
问题描述
我正在尝试制作包含自定义类的不同子类的哈希集的哈希图值的哈希图,如下所示:
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
有子类,例如:Mage
、Assassin
、Fighter
.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)
推荐答案
多级通配符 如果处理不当,有时会有点棘手.您应该首先学习如何阅读多级通配符.然后您需要学习解释多级通配符中extends
和super
边界的含义.在开始使用它们之前,您必须先学习这些重要的概念,否则您可能很快就会发疯.
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>
- YesList<Integer>
- YesList<Double>
- YesList<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 aList<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 ofList<Number>
. So, following assignments are valid for such lists:list4 = new ArrayList<Integer>(); list4 = new ArrayList<Double>();
参考:
关于单级通配符:
现在这可能在您的脑海中清晰地描绘出来,这与泛型的不变性有关.
List
不是List
,尽管Number
是Double
的超类.类似地,一个List
是- List
List
的超类.Now this might be making a clear picture in your mind, which relates back to the invariance of generics. A
List<Number>
is not aList<Double>
, althoughNumber
is superclass ofDouble
. Similarly, aList<List<? extends Number>>
is not aList<List<Integer>>
even though theList<? extends Number>
is a superclass ofList<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 fromList<List<? extends Number>>
.现在您可以将哪些元素类型添加到
superMap
中?当然,您不能将HashMap
添加到> superMap
中.为什么?因为我们不能做这样的事情:Now what all element type you can add to the
superMap
? Surely, you can't add aHashMap<String, HashSet<Assassin>>
into thesuperMap
. 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>>
tomap
and thus only put that type of map as value insuperMap
.选项 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 thesuperMap
. How? Think of it.HashMap<String, HashSet<Assassin>>
is capture-convertible toHashMap<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 declaredsuperMap
. And then your original method inAssassin
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;
这样您就可以将任何类型的
HashMap
或TreeMap
或LinkedHashMap
分配给superMap
.此外,您还可以添加HashMap
或TreeMap
作为superMap
的值.了解Liskov 替换原则的用法非常重要.So that you can assign either
HashMap
orTreeMap
orLinkedHashMap
, anytype to thesuperMap
. Also, you would be able to add aHashMap
orTreeMap
as values of thesuperMap
. It's really important to understand the usage of Liskov Substitution Principle.这篇关于带有通配符的 Java HashMap 嵌套泛型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!