如何将具有整数值的条目添加到 Map<String, ?>

How to add an entry with an Integer value into Map<String, ?>

我必须将一个整数值放入下面的地图中。

Map<String,?> map
map.put("key",2000);

当我 运行 上述代码时,出现以下错误:

incompatible types: java.lang.Integer cannot be converted to capture#1 of ?
        Map<String, Integer> map = new Map<String, Integer>;
        map.put("key",2000);

您可以将很多东西赋值给 Map<String, ?> 类型的变量。例如:

Map<String, ?> map;

map = new HashMap<String, Object>(); // works
map = new HashMap<String, String>(); // works
map = new HashMap<String, Number>(); // works
map = new HashMap<String, Integer>(); // works

一切正常。因此,假设已将 map = new HashMap<String, String>() 分配给它。然后你尝试 运行 .put("key", 2000); 就可以了。

这显然没有意义。因此,为什么该代码无法编译。

我假设你的眼球和人脑看过这段代码并确定你那里的 map 除了 Map<String, Object>Map<String, Number>Map<String, Integer> 甚至 Map<String, Serializable> - .put("key", 2000) 适合的类型。

但这不是 java 的工作方式。类型的一部分要点是限制你自己,给你自己以后分配其他东西的自由。

完全类似于此:

Object x = "hello";
x.toLowerCase();

上面的不编译: Object 类型没有toLowerCase() 方法。是的,是的,我们的眼球和大脑看着这个然后走:那是愚蠢的,当然是这样!看,x很明显指向一个字符串!

但在这种情况下,只需.. 写 String x = "hello".

因此,这同样适用于您的场景。只需写 Map<String, Integer> 即可。

很遗憾....

有时您有动态代码想要检查事物是什么并采取相应的行动。例如:

void foo(Object o) {
  if (o instanceof String s) return s.toLowerCase();
  throw new SomeException();
}

但是您不能对泛型执行此操作 - 无法 instanceof 检查泛型。你不能做这样的事情: IF 我的 map 变量指向的对象有 Integer 作为值类型(或 Integer 的任何超类型),然后做map.put("key", 2000),否则不要。

这不是泛型在 java 中的工作方式。根本没有办法做到这一点。

你唯一能做的就是下达命令。编写代码,如果它是真的就可以正常工作,但如果它不是真的,一切都会变得混乱:你可以在地图上强制 运行 .put("key", 2000),这将把它放在地图上,如果那是map 声明为 Map<String, String>,那么与此地图交互的任何代码都将开始左右抛出 ClassCastExceptions,即使它们从未投射任何东西。因为您将整数推入了字符串映射。

因此,为什么编译器会在您强制执行此操作时大喊大叫,以及为什么您不应该这样做。泛型的唯一目的是让你的编程生活更轻松,阐明 API 并捕捉错误,而你……通过强制它来做相反的事情。但是,嘿,如果你真的想这样做(极不可能):

void example(Map<String, ?> map) {
  Map forceIt = map; // raw assignment
  forceIt.put("key", 2000); // compiles
}

以上编译并执行您要求的 - 但会警告您这是一个非常糟糕的主意。

of (o instanceof String) return ((String) o).toLowerCase();

Unbounded 通用 collections (Collection<?>) 不可写。让我们来探究一下原因。

未知类型 - <?>

尖括号<?>中的问号称为未知类型。对于编译器,这意味着 实际类型 无法预测,并且在运行时它可能看起来非常匹配任何东西:ObjectString ,一个Cat。因此,您从 unbounded collection 中检索到的所有内容都将被视为 Object:

Collection<?> items = new ArrayList<>();
Object item = items.iterator().next(); // this assignment is safe and will compile

但是不要混淆一个无界collectionCollection<?>和一个collection objects Collection<Object>。它们 不兼容.

Collection<Object> 具有 不变行为 ,即它只需要 collection 类型 Object ] 分配给它。同时,我们可以将any类型的collection赋值给Collection<?>.

Collection<?> items = new ArrayList<String>();         // OK

Collection<Object> objects1 = items;                   // that will not compile
Collection<Object> objects2 = new ArrayList<String>(); // that will not compile
Collection<Object> objects3 = new ArrayList<Object>(); // OK - compiler will not complain here

因此,未知类型 可以用 扩展 Object 的任何东西表示。您可能会认为 Collection<?> 好像是 upper-bounded collection Collection<? extends Object>。它们绝对兼容.

Collection<?> items = new ArrayList<>();
Collection<? extends Object> upperBounded = items; // no issues
items = upperBounded;                              // no issues

修改无界collection

Wild-cards 像 <?> <? extends Object> 不是实际类型。它们只是告诉编译器我们不知道 type 在运行时会是什么的一种方式。

那么Collection<?>实际类型是什么?

因为已经很清楚 未知类型 (<?>) 意味着它可以是 任何类型 扩展 Object,因此在 Collection<?> 的背后可能会出现 ArrayList<Object>ArrayList<String>HashSet<BigDecimal>

Remainder:参数化的collection会在运行时伪装成Collection<?>invariant,这意味着 ArrayList<String> 预计仅包含 Strings,而 HashSet<BigDecimal> 预计仅存储 BigDecimal 的实例。但是没有办法在运行时确保这一点,因为在编译期间泛型参数 get erased。因此,在使用无限泛型 collection 的同时提供 type-safety 的唯一方法是不允许向其中添加任何内容。

例如,由于将 IntegerObject 添加到 ArrayList<String> 或添加实例 是非法的 StringHashSet<BigDecimal> 编译器将 阻止 任何尝试 添加 任何东西到 Collection<?>。因为没有与 未知类型.

兼容的类型

只有一个例外,null 是任何类型 object 的有效值,因此它也可以添加到 unbounded 中作为 upper-bounded collection.

Collection<?> items = new ArrayList<>();
items.add(null);
System.out.println(items);
items.remove(null);
System.out.println(items.isEmpty());

将给出一个输出:

[null]  -  null-element was successfuly added
true    -  i.e. isEmpty

注意:没有 删除 元素的限制 unboundedupper-bounded collection s.

备选方案

因此,没办法无限映射添加新条目,您可以要么一个一个地复制其条目,执行 instanceOf 检查到常规通用映射 Map<String, Integer> 通过将映射分配给 行类型 [=143= 的变量来完全摆脱泛型],然后在需要检索 value 时手动添加 type-casts,如 Java 1.4.

如何将 unbounded map 的内容复制到 regular 并利用泛型的优点。

Map<String, ?> unboundedMap = new HashMap<>()

Map<String, Integer> writableMap = new HashMap<>();

for (Map.Entry<String, ?> entry: unboundedMap.entrySet()) {
    if (entry.getValue() instanceof Integer) {
        writableMap.put(entry.getKey(), (Integer) entry.getValue());
    }
}

使用 Stream 也一样API

Map<String, Integer> writableMap =
       unboundedMap.entrySet().stream()
                   .filter(entry -> entry.getValue() instanceof Integer)
                   .collect(Collectors.toMap(Map.Entry::getKey,
                                             entry -> (Integer) entry.getValue()));