Почему существуют разные способы обращения к ключам 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 создаст другой хеш-код