2

公式ドキュメントで Groovy (2.4.4) 構文を調べているときに、識別子として GString を使用したマップに関する特別な動作に遭遇しました。ドキュメントで説明されているように、評価されていない GString オブジェクトのハッシュコードは、評価された GString と同じ表現を持つ通常の String オブジェクトとは異なるため、GString は (ハッシュ) マップ識別子としては悪い考えです。

例:

def key = "id"
def m = ["${key}": "value for ${key}"]

println "id".hashCode() // prints "3355"
println "${key}".hashCode() // prints "3392", different hashcode

assert m["id"] == null // evaluates true

ただし、私の直感的な期待は、実際の G​​String 識別子を使用してマップ内のキーをアドレス指定すると、実際には値が配信されるというものでしたが、そうではありませんでした。

def key = "id"
def m = ["${key}": "value for ${key}"]

assert m["${key}"] == null // evaluates also true, not expected

それが気になりました。そのため、この問題に関していくつかの提案があり、いくつかの実験を行いました。

(私は Groovy を初めて使用するので、その場でブレインストーミングを行っただけであることを覚えておいてください。問題の原因を調べようとした方法を読みたくない場合は、提案 #4 に進んでください)

提案#1。GString オブジェクトのハッシュコードは、何らかの理由で動作/実装されており、コンテキストまたは実際のオブジェクトに応じて異なる結果を提供します。

それはナンセンスであることがすぐに判明しました。

println "${key}".hashCode() // prints "3392"
// do sth else
println "${key}".hashCode() // still "3392"

提案#2。マップまたはマップ アイテムの実際のキーに、期待される表現またはハッシュコードがありません。

マップ内のアイテム、キー、およびそのハッシュコードを詳しく調べました。

println m // prints "[id:value for id]", as expected
m.each { 
    it -> println key.hashCode() 
} // prints "3355" - hashcode of the String "id"

したがって、マップ内のキーのハッシュコードは GString ハッシュコードとは異なります。はっ!か否か。知っておくと便利ですが、マップ インデックス内の実際のハッシュコードをまだ知っているので、実際には関係ありません。インデックスに入れられた後に文字列に変換されたキーを再ハッシュしました。じゃあ他には?

提案#3。GString の equals-method には、不明または実装されていない動作があります。

2 つのハッシュコードが等しいかどうかに関係なく、マップ内の同じオブジェクトを表すとは限りません。これは、キー オブジェクトのクラスに対する equals メソッドの実装に依存します。たとえば、equals メソッドが実装されていない場合、ハッシュコードが同一であっても 2 つのオブジェクトは等しくないため、目的のマップ キーを適切にアドレス指定できません。だから私は試しました:

def a = "${key}"
def b = "${key}"

assert a.equals(b)  // returns true (unfortunate but expected)

したがって、同じ GString の 2 つの表現はデフォルトで等しくなります。

試した他のいくつかのアイデアを飛ばして、この投稿を書く直前に試した最後のアイデアを続けます。

提案#4。アクセスの構文は重要です。

それは理解の真のキラーでした。私は前に知っていました: 2 つのマップ値にアクセスする方法は構文的に異なります。それぞれの方法には制限がありますが、結果は同じままだと思いました。さて、これが出てきました:

def key = "id"
def m = ["${key}": "value for ${key}"]

assert m["id"] == null // as before
assert m["${key}"] == null // as before
assert m.get("${key}") == null // assertion fails, value returned

したがって、マップの get メソッドを使用すると、最初に期待した方法で実際の値を取得できます。

GStrings に関するこのマップ アクセス動作の説明は何ですか? (または、ここにどのような新人ミスが隠されていますか?)

お待ち頂きまして、ありがとうございます。

編集:私の実際の質問が明確に述べられていないことを恐れているので、ここに簡潔で簡潔なケースがあります:

このようなキーとして GString を持つマップがある場合

def m = ["${key}": "value for ${key}"]

なぜこれは値を返すのですか

println m.get("${key}")

しかし、そうではありません

println m["${key}"]

?

4

1 に答える 1

1

この問題は、非常に異なるアプローチで見ることができます。マップの実装はこれに依存しているため、マップは (少なくとも hashcode と equals に対して) 不変のキーを持つことになっています。GString は変更可能であるため、一般的にマップ キーにはあまり適していません。String#equals(GString) の呼び出しの問題もあります。GString は Groovy クラスであるため、equals メソッドに影響を与えて String に等しくすることができます。しかし、ストリングは大きく異なります。つまり、hashcode() が String と GString で同じように動作する場合でも、GString を使用して String で equals を呼び出すと、Java の世界では常に false になります。次に、文字列キーを持つマップを想像してください。マップに GString を持つ値を要求します。常に null を返します。一方、文字列でクエリされた GString キーを持つマップは、「適切な」値を返す可能性があります。

そして、この問題のため、GString#hashCode() は意図的に String#hashCode() と等しくありません。

決して非決定論的ではありませんが、参加しているオブジェクトが toString 表現を変更すると、GString ハッシュコードが変更される可能性があります。

def map = [:]
def gstring = "$map"
def hashCodeOld = gstring.hashCode()
assert hashCodeOld == gstring.hashCode()
map.foo = "bar"
assert hashCodeOld != gstring.hashCode()

ここで、map の toString 表現が Groovy と GString で変更されるため、GString は異なるハッシュコードを生成します。

于 2015-07-30T09:39:15.973 に答える