2013-03-07 4 views
16

This Java tutorial говорит, что неизменный объект не может изменить свое состояние после создания.Правильно ли вызывать java.lang.String неизменяемым?

java.lang.String имеет поле

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

, который инициализируется при первом вызове метода hashCode(), поэтому она меняется после создания:

String s = new String(new char[] {' '}); 
    Field hash = s.getClass().getDeclaredField("hash"); 
    hash.setAccessible(true); 
    System.out.println(hash.get(s)); 
    s.hashCode(); 
    System.out.println(hash.get(s)); 

выходного

0 
32 

Является ли это правильно позвонить String неизменяемым?

+17

Отражающие хаки не считаются неизменными. – Perception

+0

http://stackoverflow.com/q/11146255/758280 – Jeffrey

+0

Как говорится в @Perception, рефлексивные хаки не должны учитываться. Кэширование хэш-значения в частном поле не влияет на какие-либо не частные методы или состояние. –

ответ

8

Термин «неизменяемый» является достаточно расплывчатым, чтобы не допускать точного определения.

Предлагаю прочитать дневник Kinds of Immutability из блога Эрика Липперта. Хотя это технически статья C#, это весьма актуально для поставленного вопроса. В частности:

Наблюдательная неизменность:

Предположим, что у вас есть объект, который имеет свойство, что каждый раз, когда вызове метода на ней, смотреть на поле, и т.д., вы получите такой же результат. С точки зрения вызывающего объекта такой объект будет неизменным. Однако вы могли представить себе, что за кулисами объект выполнял ленивую инициализацию, запоминая результаты вызовов функций в хеш-таблице и т. Д. «Кишки» объекта могут быть полностью изменчивыми.

Какое это имеет значение? Поистине глубоко неизменные объекты никогда не меняют свое внутреннее состояние и, следовательно, по сути являются потокобезопасными. Объект , который может быть изменен за кулисами, все равно должен иметь сложный код потоковой передачи, чтобы защитить его внутреннее изменяемое состояние от повреждения, если объект должен вызываться в двух потоках «при в то же время».

+0

Я предполагаю, что, если вы не являетесь абсолютным, вопрос о «неизменности» зависит от того, с какой точки зрения (то есть какого объекта) его спрашивают. – ArtB

0

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

0

Отражение позволит вам изменить содержимое любого частного поля. Поэтому правильно ли вызывать любой объект в Java неизменным?

Неизменяемость относится к изменениям, которые инициируются или воспринимаются приложением.

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

+1

На самом деле я использовал публичный метод для изменения состояния строки –

+0

@EvgeniyDorofeev - не в примере, который вы показали. По крайней мере, это не общедоступный метод класса * String *. Если существует общедоступный метод класса * String *, который позволяет вам изменять его состояние, я соглашусь: это не является неизменным. Но я не знаю такого метода. – parsifal

+0

, но я вызвал общедоступный метод hashCode() в примере, а поле hash изменилось. –

0

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

Это неизменно по дизайну. Если вы используете Reflection (получение объявленного поля и сброс его доступности), вы обойдете его дизайн.

1

Да, это правильно назвать их неизменяемыми.

Хотя это правда, что вы можете достичь в и изменить private ... и final ... переменные класса, он является ненужным и невероятно неразумно, что нужно сделать на String объекта. Обычно предположил, что никто не будет достаточно сумасшедшим.

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

Следует также отметить, что JLS заявляет, что использование отражения для изменения final может нарушить работу (например, в многопоточном режиме) или может не иметь никакого эффекта.

+0

Я использовал общедоступный метод hashCode(), чтобы изменить хэш-поле. Документы говорят, что неизменяемые объекты не могут их состояние после создания. Является ли поле хэш-поля частью экземпляра String или нет? –

+0

@EvgeniyDorofeev: За некоторыми исключениями (например, методами 'delay') тот факт, что метод требует времени для выполнения, не считается частью его поведения. Единственное наблюдаемое различие между «строковым» объектом, чье хэш-поле равным нулю, а другое с хэш-полем - будет временем, требуемым для выполнения его метода hashCode(). Обратите внимание, что если вычисленное значение хеширования каким-то образом зависело от того, когда был выполнен первый вызов 'hashCode()', это * будет * представлять изменяемое состояние, но это не так. – supercat

+0

@EvgeniyDorofeev - В случае, о котором вы говорите ('String'), ответ зависит от вашей перспективы. См. Ответ Ани. Также помните, что учебник по Java не является спецификацией. Это упрощенная версия информации в (более) окончательных документах. Если вы посмотрите на JLS, * неизменяемость * не является основным свойством. Скорее это свойство класса, которое возникает из того, как дизайн API классов и его реализация. Основная цель учебника Java - помочь новичкам ... не быть окончательным текстом. –

3

После создания все методы в экземпляре String (вызываемые с теми же параметрами) всегда будут иметь одинаковый результат. Вы не можете изменить свое поведение (с помощью любого общедоступного метода), поэтому он всегда будет представлять один и тот же объект. Также это final и не может быть подклассом, поэтому гарантируется, что все экземпляры будут вести себя следующим образом.

Таким образом, с общественной точки зрения объект считается неизменным. В этом случае внутреннее состояние не имеет большого значения.

+0

Ах да, но в этом случае он использует хитрые трюки, чтобы изменить переменную 'hash', а переменную' hash' можно наблюдать через 'hashcode()'. Таким образом, по вашему определению, String будет изменчивым. –

+0

Я не повторил первый комментарий, что * отражение не считается * даже в моем определении. – gaborsch

13

Лучшее определение было бы не, что объект не изменения, но она не может быть наблюдается были изменены. Это поведение никогда не изменится: .substring(x,y) всегда будет возвращать то же самое для этой строки, что и для equals, и все остальные методы.

Эта переменная рассчитывается при первом вызове .hashcode() и кэшируется для дальнейших вызовов. Это в основном то, что они называют «memoization» в языках функционального программирования.

Отражение на самом деле не инструмент для «программирования», а скорее метапрограммирование (т. Е. Программы программирования для генерации программ), поэтому оно действительно не учитывается. Это эквивалентно изменению значения константы с помощью отладчика памяти.

0

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

1

С точки зрения разработчика, использующего отражение, это не правильно позвонить String неизменный. Есть реальные Java-разработчики, которые используют рефлекс для ежедневного написания реального программного обеспечения. Отклонение отражения как «взлома» нелепо. Однако, с точки зрения разработчика, который не использует отражение, правильно называть String неизменяемым. Независимо от того, действительно ли предполагается, что String является неизменным, зависит от контекста.

Неизменяемость является абстрактным понятием и поэтому не может применяться в абсолютном смысле к чему-либо с физической формой (см. ship of Theseus). Конструкции языка программирования, такие как объекты, переменные и методы, существуют физически как биты на носителе данных. Деградация данных - это физический процесс, который происходит со всеми носителями, поэтому никакие данные никогда не могут считаться действительно неизменными. Кроме того, практически практически на практике возможно подорвать функции языка программирования, предназначенные для предотвращения мутации конкретной даты. В противоположность этому, число 3 является 3, всегда было 3, и всегда будет 3.

Применительно к программе данные, неизменность следует считать полезным предположение, а не фундаментальное свойство. Например, если предположить, что String является неизменяемым, можно кэшировать его хеш-код для повторного использования и избежать затрат на повторное вычисление его хеш-кода позже. Практически все нетривиальное программное обеспечение полагается на предположения, что определенные данные не будут мутировать в течение определенных продолжительностей времени. Разработчики программного обеспечения обычно предполагают, что программа code segment не изменится во время ее выполнения, если только они не написали самомодифицирующий код. Понимание того, какие предположения действительны в конкретном контексте, является важным аспектом разработки программного обеспечения.

Смежные вопросы