Подтвердить что ты не робот

Почему класс String неизменен, хотя у него есть нефинальное поле, называемое "хэш",

Я читал статью 15 из Эффективная Java от Джошуа Блоха. Внутри пункта 15, в котором говорится о "минимизации изменчивости", он упоминает пять правил, чтобы сделать объекты неизменными. Один из них - сделать все поля окончательными. Вот правило:

Сделать все поля окончательными: это четко выражает ваши намерения таким образом, который соблюдается системой. Кроме того, необходимо обеспечить правильное поведение, если ссылка к вновь созданному экземпляру передается из одного потока в другой без синхронизация, как указано в модели памяти [JLS, 17,5; Goetz06 16].

Я знаю, что класс String является примером неизменяемого класса. Просматривая исходный код я вижу, что на самом деле он имеет экземпляр хэша, который не является окончательным.

//Cache the hash code for the string
private int hash; // Default to 0

Как String становится неизменным тогда?

4b9b3361

Ответ 1

В примечании объясняется, почему это не является окончательным:

//Кэш хэш-кода для строки

Это кеш. Если вы не вызываете hashCode, значение для него не будет установлено. Он мог быть установлен во время создания строки, но это означало бы более длительное время создания, для функции, которая вам может не понадобиться (хэш-код). С другой стороны, было бы бесполезно вычислять хеш каждый раз, когда его спрашивали, дать строку неизменной, а хэш-код никогда не изменится.

Тот факт, что там не финальное поле несколько противоречит указанному вами определению, но здесь он не является частью интерфейса объекта. Это просто внутренняя деталь реализации, которая не влияет на изменчивость строки (в виде контейнера символов).

Изменить - из-за популярного спроса, завершая мой ответ: хотя hash не является непосредственно частью открытого интерфейса, он мог повлиять на поведение этого интерфейса, так как hashCode возвращает его значение. Теперь, поскольку hashCode не синхронизирован, возможно, что hash устанавливается более одного раза, если несколько потоков используют этот метод одновременно. Однако значение, установленное на hash, всегда является результатом стабильного вычисления, который зависит только от конечных полей (value, offset и count). Поэтому каждый расчет хеширования дает точный результат. Для внешнего пользователя это так, как если бы hash вычислялся один раз - и точно так же, как если бы он был рассчитан каждый раз, так как контракт hashCode требует, чтобы он последовательно возвращал тот же результат для данного значения. Итог, хотя hash не является окончательным, его изменчивость никогда не видна внешнему зрителю, поэтому класс можно считать неизменным.

Ответ 2

String является неизменным, поскольку в отношении его пользователей он никогда не может быть изменен и всегда будет выглядеть одинаково для всех потоков.

hashCode() вычисляется с использованием яркой одноименной идиомы (элемент EJ 71), и это безопасно, потому что это не причиняет вреда никому, если hashCode() вычисляется более одного раза случайно.

Создание всех полей final - это самый простой и простой способ сделать классы неизменяемыми, но это не требуется строго. Пока все методы возвращают одно и то же, независимо от того, какой поток вызывает его, когда класс неизменен.

Ответ 3

Несмотря на то, что String неизменен, он может меняться через отражение. Если вы делаете хэш-финал, вы могли бы портить вещи по-королевски, если бы это произошло. Хэш-поле также отличается тем, что оно существует в основном как кеш, способ ускорить вычисление hashCode(), и его действительно следует рассматривать как вычисленное поле, а не константу.

Ответ 4

Существует много ситуаций, в которых может быть полезно, чтобы класс, который является логически неизменным, имеет несколько разных представлений для одного и того же наблюдаемого состояния, а для экземпляров класса возможность переключаться между ними, Значение hashcode, которое будет возвращено из строки, чье поле хэша равно нулю, будет таким же, как и значение, которое будет возвращено, если хэш-поле провело результат более раннего вызова хэш-кода. Следовательно, изменение хэш-значения от первого к последнему не изменит состояние наблюдаемого объекта, но приведет к более быстрому выполнению будущих операций.

Самые большие трудности с кодированием в этом случае -

  1. Если объект изменен от ссылки на какой-либо конкретный неизменяемый объект на привязку к другому объекту с идентичным семантическим контентом, такое изменение не должно влиять на наблюдаемое состояние объекта, содержащего ссылку, но если окажется, что предположительно идентичный объект не был действительно идентичным, могут произойти плохие вещи, особенно если объект, предположительно содержащий ссылку, считается заменяемым для других семантически идентичных объектов.
  2. Даже если нет ошибок, в которых объекты "идентичны", может все еще быть опасность того, что объекты, которые кажутся идентичными потоку, который делает замену, могут не совпадать с другими потоками. Этот сценарий вряд ли произойдет, но если это произойдет, эффекты могут быть очень плохими.

    Тем не менее, могут быть некоторые преимущества для создания подстановок неизменных объектов. Например, если программа будет сравнивать многие объекты, которые содержат длинные строки, и многие из них, хотя и отдельно созданные, будут идентичны друг другу, может быть полезно использовать WeakDictionary для создания пула различных экземпляров строк, и замените любую строку, которая, как установлено, идентична одной в пуле со ссылкой на копию пула. Это приведет к тому, что многие строки будут идентичны для одной и той же строки, тем самым значительно ускоряя любые будущие сравнения, которые могут быть сделаны между ними. Конечно, как было отмечено, очень важно, чтобы объекты были корректно логически неизменными, чтобы сравнения выполнялись правильно. Любые проблемы в этом отношении могут превратить оптимизацию в беспорядок.

Ответ 5

Чтобы создать неизменяемый объект, вы должны сделать окончательный класс и все его члены окончательными, чтобы после того, как объекты будут разбиты, никто не может изменить его состояние. Вы можете достичь такой же функциональности, сделав член несинтетическим, но приватным и не изменяя их, кроме конструктора.

EDIT:

Примечание:  При хэшировании строки Java также кэширует хэш-значение в хэш-атрибуте, но , только если результат отличается от нуля.