Почему существуют разные способы обращения к ключам GString в картах?

Изучая синтаксис Groovy (2.4.4) в официальной документации, я столкнулся со специальным поведением, связанным с картами с GStrings в качестве идентификаторов. Как описано в документации, GStrings являются плохой идеей как (хеш) идентификаторы карты, потому что хэш-коды не оцененного объекта 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

Тем не менее, мое интуитивное ожидание состояло в том, что использование фактического идентификатора GString для адресации ключа на карте фактически даст значение, но это не так.

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. Метод equals GString имеет неизвестное или не реализованное поведение.

Независимо от того, равны ли два хеш-кода, они могут не представлять один и тот же объект на карте. Это зависит от реализации метода equals для класса ключа-объекта. Если метод equals, например, не реализован, два объекта не равны, даже если хеш-код идентичен и, следовательно, требуемый ключ карты не может быть адресован должным образом. Итак, я попробовал:

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

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

Таким образом, два представления одной и той же строки GString по умолчанию равны.

Я пропускаю некоторые другие идеи, которые я попробовал, и продолжаю с последней вещи, которую я попробовал непосредственно перед тем, как собирался написать этот пост.

Предложение № 4. Синтаксис доступа имеет значение.

Это был настоящий убийца понимания. Я знал раньше: есть синтаксически разные способы доступа к двум значениям карты. У каждого способа есть свои ограничения, но я думал, что результаты останутся прежними. Ну вот и подошло

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}"]

?

1 ответ

Вы можете взглянуть на это с совершенно другого подхода. Предполагается, что карта имеет неизменные ключи (по крайней мере, для хэш-кода и равно), потому что от этого зависит реализация карты. GString изменчива, поэтому не очень подходит для ключей карты в целом. Существует также проблема вызова String#equals(GString). GString является классом Groovy, поэтому мы можем просто повлиять на метод equals, чтобы он равнялся String. Но String совсем другой. Это означает, что вызов equals для String с GString всегда будет ложным в мире Java, даже если hashcode() будет вести себя одинаково для String и GString. А теперь представьте карту со строковыми ключами и спросите у карты значение GString. Это всегда будет возвращать ноль. С другой стороны, карта с ключами GString, запрашиваемая со строкой, может вернуть "правильное" значение. Это означает, что всегда будет отключение.

И из-за этой проблемы GString#hashCode() не равен String # hashCode () специально.

Он никоим образом не является недетерминированным, но хеш-код GString может измениться, если участвующие объекты изменят свое представление toString:

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

Здесь представление карты toString изменится для Groovy и GString, поэтому GString создаст другой хеш-код

Другие вопросы по тегам