当前位置: 首页 > 知识库问答 >
问题:

Map界面中的上限通配符

燕和裕
2023-03-14

我有下面的地图

Map<Integer, ? extends Collection<Integer>> map

有时有一个列表,有时有一个集合作为值。现在我想获取值,但有一个问题,它无法编译。

Collection<Integer> value = map.getOrDefault(1, Collections.emptyList())

我知道我可以这样做:

Collection<Integer> value = map.get(1);
if (value == null) {
     value = Collections.emptyList();
}

但我有兴趣了解它应该如何与通用getOrDefault方法一起工作

共有2个答案

郦祺
2023-03-14

它的缺点是:您根本不能将“V”声明为“code”的任何映射使用getOrDefault 或<代码>?扩展任何内容。克隆JDK源代码或使用自己的方法创建实用方法来解决这一问题是不可能的。

可以想象一个getOrDefault实现不会有这个问题。不幸的是,OpenJDK源代码根本没有正确编写它,或者我没有看到一些权衡。不幸的是,“修复它”向后不兼容,所以它可能永远不会发生;我们被损坏的getOrDefault卡住了。谢谢你的这个问题——通过思考它,它让我意识到它坏了。我现在为没有在询问反馈时提供有关amber-dev的反馈而感到难过:)

原因的解释有点详细,需要良好的直觉和泛型如何工作的知识。戴上你的思考帽,如果你感兴趣,继续读下去!

Map<Integer, ? extends Collection<Integer>> map

好的:这意味着这个变量可以指向,例如,一个新的HashMap

<代码>贴图。getOrDefault(..)

啊。这是不可能的。您需要提供一个默认值,并且无论地图实际指向什么,这个默认值都必须“工作”——它需要与此地图中的值类型相同。想象一下你有一个Map

答案是什么都没有。

你不能这样做。这里根本不能使用getOrDefault。好吧,除非你只是简单地传递null,这是唯一一个“所有类型”的值,但是你应该只写。获取(k)而不是。getOrDefault(k,null)当然可以。

这是getOrDefault的实现,直接从OpenJDK源代码粘贴:

  default V getOrDefault(Object key, V defaultValue) {
     V v;
     return (((v = get(key)) != null) || containsKey(key))
       ? v
       : defaultValue;
  }

这从根本上永远不会对您起作用-如果V是,您就不能有V?任何-that?意思是:我们不知道类型是什么,也没有typevar表示它,所以没有表达式可以适合。getOrDefault的这种做法在这里可以很好地工作:

default <Z super V> getOrDefault(Object key, Z defaultValue) {
  Z z;
  return (((z = get(key) != null) || containsKey(key))
    ? z
    : defaultValue;
}

这里发生的事情是,在这种情况下,虽然映射可能是一个集合或列表等,但您只希望它作为集合

因此,我们需要确定存在一些新的类型Z,它是V的超类型(从而保证如果键在映射中,您会得到一个V,这绝对是一种Z,因为根据声明,Z是V的超类型),并且有一个默认值也绝对是Z类型,从而保证在任何一个“分支”(找到键,但找不到键)中,返回的值至少为Z。

但是,map并不是这样工作的,因此您不能使用。getOrDefault(获取默认值)。

我不认为此时修改getOrDefault是向后兼容的,所以我认为向openjdk核心团队提交特性请求没有任何意义:他们只会拒绝它,我们坚持使用getOrDefault。但是,如果必须使用静态实用程序方法来执行上述操作,则可以创建静态实用程序方法。请注意,您可能希望以不同的方式编写它—强制转换为原始,在原始模式下执行工作,忽略警告,然后进行清理。理论上,一些Map实现可能会有不同的getOrDefault实现,尽管我很难想象它会是什么样子(比如说,与computeIfAbsent不同,computeIfAbsent绝对有重要的自定义实现,例如在ConcurrentHashMap中)。

董建德
2023-03-14

这是一个非常有争议的案例——简而言之,您不能让getOrDefalt()使用上界值。

default V getOrDefault(Object key, V defaultValue)

作为第二个参数,getOrDefalt()期望V类型的实例。这意味着参数应该与Value的类型完全匹配,它是如何声明的,在这种情况下是?扩展集合

我们无法使用通配符执行类型转换。此代码在语法上无效:

Collection<Integer> value = 
    m.getOrDefault(1, (? extends Collection<Integer>) Collections.emptyList()); // will not compile

无法提供所需类型的默认值。

@rzwitserloot如果我错了,请你指出我的错觉。如果有一种方法可以使用具有上限值的getOrDefalt(),那么我真的很想看到你的答案。

 类似资料:
  • 问号(?)代表通配符,代表泛型中的未知类型。 有时您可能希望限制允许传递给类型参数的类型。 例如,对数字进行操作的方法可能只想接受Number或其子类的实例。 要声明一个上限的Wildcard参数,请列出?,后跟extends关键字,后跟其上限。 例子 (Example) 以下示例说明了如何使用extends来指定上限通配符。 package com.wenjiangs; import java.

  • null 为什么我不能在MyList中添加对象。因为如果我们使用super,这意味着这个列表可以包含在Java类的继承制度中等于或高于number的对象。因此应该按照该语句在列表中添加新的Object()。 多谢了。

  • 上限通配符下限通配符 有人能帮我理解一下吗?

  • 类型参数T不应由最终的类型整数限定。不能进一步扩展Final类型 为什么使用final类型作为通配符的上限完全可以,但对类型参数抛出警告?为什么Java甚至允许通配符被最终的上层类型所约束?

  • null 编译,我真的不明白为什么。基本上与第1行相同的问题。是的超类,如何将超类的成员放入此中? 编译。与第1行相同的问题。

  • 为了进一步说明受限制于Number的泛型将接受Number或它的任何子类的类型参数: 鉴于此,我看到使用上限有界通配符与有界泛型相比的唯一好处是,可以声明上限有界通配符类型参数,而不依赖于类或方法已经声明的类型参数。还有更重要的好处我错过了吗?