为什么 Map 不适用于 Groovy 中的 GString?

Why Map does not work for GString in Groovy?

使用以下代码片段我无法从地图中检索 gString

def contents = "contents"
def gString = "$contents"

def map = [(gString): true]

assert map.size() == 1 // Passes
assert gString.hashCode() == map.keySet().first().hashCode() // Passes, same hash code
assert map[gString] // Fails

这怎么可能?

断言消息清楚地表明 Groovy 存在严重错误:

assert map[gString] // Fails
       |  ||
       |  |contents
       |  null
       [contents:true]

这与 Why groovy does not see some values in dictionary? 不是同一个问题 第一个答案建议:

You're adding GString instances as keys in your map, then searching for them using String instances.

在这个问题中我明确添加GString并尝试检索GString

而且 Why are there different behaviors for the ways of addressing GString keys in maps? nor Groovy different results on using equals() and == on a GStringImpl 都没有答案。我不改变任何东西,我不混合 StringGString

Groovy没问题。 GString 不是 字符串。它是可变的,因此永远不应该用作映射中的键(就像 Java 中的任何其他可变对象一样)。

在文档中了解更多相关信息:http://docs.groovy-lang.org/latest/html/documentation/index.html#_gstring_and_string_hashcodes

tl;dr: 您似乎发现了 Groovy 的运行时参数重载评估中的错误。

答案:

map[gString] 在运行时直接通过 Groovy 的运算符重载机制被评估为 map.getAt(gString)。到目前为止,一切都很好,但现在一切都开始出错了。 Java LinkedHashMap class 在其类型层次结构中的任何地方都没有 getAt 方法,因此 Groovy 必须使用动态关联的混合方法(实际上该语句是有点相反。Groovy 使用 mixin 方法 before 使用 class 层次结构中声明的方法。)

因此,长话短说,Groovy 解析 map.getAt(gString) 使用类别方法 DefaultGroovyMethods.getAt()。简单易行,对吧?除了此方法有大量不同的参数重载,其中一些可能适用,尤其是当您考虑 Groovy 的默认参数强制时。

不幸的是,Groovy 没有选择似乎是完美匹配的 DefaultGroovyMethods.getAt(Map<K,V>,K),而是选择了 DefaultGroovyMethods.getAt(Object,String),它将 GString 关键参数强制转换为 String。由于实际的键实际上是一个GString,该方法最终无法找到该值。

对我来说,真正的杀手锏是,如果直接从代码执行参数重载解析(而不是在运算符解析和类别方法选择之后),那么 Groovy 会做出正确的重载选择!也就是说,如果你替换这个表达式:

map[gString]

用这个表达式:

DefaultGroovyMethods.getAt(map,gString)

然后正确解析参数重载,找到并返回正确的值。