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

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

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

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

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

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

5 ответов


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

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

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

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

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


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

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

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


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


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

самые большие трудности с кодированием вещей такими способами -

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

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


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

изменить:

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