Что такое неизменяемые объекты?
Объект String является неизменяемым, но это означает, что вы не можете изменить его значение, вызвав его общедоступные методы.
Как мы все знаем, в Java класс String неизменяем. Так что же такое неизменяемые объекты? Вы можете думать об этом так: если объект не может изменить свое состояние после создания, то этот объект является неизменяемым. Состояние не может быть изменено, что означает, что переменные-члены внутри объекта не могут быть изменены, включая значения базовых типов данных. Переменные ссылочных типов не могут указывать на другие объекты, а состояние объектов, на которые указывают ссылочные типы, не может быть изменено. быть изменено.
Различать объекты и ссылки на объекты
У новичков в Java всегда возникают сомнения относительно того, что String является неизменяемым объектом. Посмотрите на код ниже:
String s = "ABCabc";System.out.println("s = " + s);s = "123456";System.out.println("s = " + s);Результат печати:
с = ABCabc с = 123456
Сначала создайте объект String s, затем пусть значение s будет «ABCabc», а затем пусть значение s будет «123456». Как видно из распечатанных результатов, значение s действительно изменилось. Так почему же вы все еще утверждаете, что объекты String неизменяемы? На самом деле здесь есть недоразумение: s — это всего лишь ссылка на объект String, а не сам объект. Объект представляет собой область памяти в памяти. Чем больше переменных-членов, тем больше места занимает эта область памяти. Ссылка — это всего лишь 4-байтовые данные, в которых хранится адрес объекта, на который она указывает. Доступ к объекту осуществляется по этому адресу.
Другими словами, s — это просто ссылка, указывающая на конкретный объект. Когда s="123456"; после выполнения этого кода создается новый объект «123456», и ссылка s снова указывает на этот объект. , исходный объект «ABCabc» все еще существует в памяти и не изменился. Структура памяти показана на рисунке ниже:
Одно из различий между Java и C++ заключается в том, что в Java невозможно напрямую управлять самим объектом. На все объекты указывает ссылка. Эту ссылку необходимо использовать для доступа к самому объекту, включая получение значения переменной-члена и его изменение. переменная-член объекта. Вызов методов объекта и т. д. В C++ есть три вещи: ссылки, объекты и указатели, и все они могут обращаться к объектам. Фактически ссылки в Java и указатели в C++ концептуально схожи. Они представляют собой значения адресов хранящихся в памяти объектов. Однако в Java ссылки теряют некоторую гибкость. Например, ссылки в Java нельзя использовать как сложение и вычитание. выполняются как указатели в C++.
Почему объекты String неизменяемы?
Чтобы понять неизменность String, сначала взгляните на переменные-члены класса String. В JDK1.6 переменные-члены String включают следующее:
общедоступный финальный класс String реализует java.io.Serializable, Comparable<String>, CharSequence{ /** Значение используется для хранения символов */ Private Final char value[] /** Смещение — это первый индекс хранилища. которое используется. */ Private Final int offset; /** Счетчик — это количество символов в строке. */ Private Final int count; /** Кэшируем хеш-код для строки */ Private int hash; По умолчанию 0В JDK1.7 в класс String были внесены некоторые изменения, в основном изменено поведение при выполнении метода substring, что не имеет отношения к теме этой статьи. В JDK1.7 есть только две основные переменные-члены класса String:
общедоступный финальный класс String реализует java.io.Serializable, Comparable<String>, CharSequence { /** Значение используется для хранения символов */ Private Final char value[]; /** Кэшируем хеш-код для строки */ частный int hash // По умолчанию 0Как видно из приведенного выше кода, класс String в Java на самом деле представляет собой инкапсуляцию массива символов. В JDK6 значение — это массив, инкапсулированный строкой, смещение — это начальная позиция строки в массиве значений, а счетчик — это количество символов, занимаемых строкой. В JDK7 имеется только одна переменная значения, то есть все символы в значении принадлежат объекту String. Данное изменение не влияет на обсуждение данной статьи. Кроме того, существует хеш-переменная-член, которая представляет собой кэш хеш-значения объекта String. Эта переменная-член также не имеет отношения к обсуждению этой статьи. В Java массивы также являются объектами (см. мою предыдущую статью «Характеристики массивов в Java»). Таким образом, значение — это просто ссылка, указывающая на реальный объект массива. Фактически, после выполнения кода String s = «ABCabc»; реальная структура памяти должна быть такой:
Все три переменные value, offset и count являются частными, и для изменения этих значений не предусмотрены общедоступные методы, такие как setValue, setOffset и setCount, поэтому String нельзя изменить вне класса String. То есть после инициализации его нельзя изменить, и к этим трем членам нельзя получить доступ за пределами класса String. Кроме того, три переменные value, offset и count являются окончательными, а это означает, что внутри класса String после инициализации этих трех значений их нельзя изменить. Таким образом, объект String можно считать неизменяемым.
Итак, в String, очевидно, есть некоторые методы, и вызов их может получить измененное значение. К этим методам относятся substring, replace, replaceAll, toLowerCase и т. д. Например, следующий код:
String a = "ABCabc"; System.out.println("a = " + a); a = a.replace('A', 'a'); System.out.println("a = " + a);Результат печати:
а = ABCabca = aBCabc
Потом значение a вроде бы изменилось, но на самом деле это то же самое недоразумение. Опять же, a — это просто ссылка, а не настоящий строковый объект. При вызове a.replace('A', 'a') метод внутренне создает новый объект String и переназначает новый объект Cited a. Исходный код метода replace в String может проиллюстрировать проблему:
Читатели могут самостоятельно проверить другие методы. Все они заново создают новый объект String внутри метода и возвращают этот новый объект. Исходный объект не будет изменен. Вот почему такие методы, как replace, substring, toLowerCase и т. д., имеют возвращаемые значения. Именно поэтому следующий вызов не меняет значение объекта:
String ss = "123456";System.out.println("ss = " + ss);ss.replace('1', '0');System.out.println("ss = " + ss);Распечатайте результат:
сс = 123456 сс = 123456
Действительно ли объекты String неизменяемы?
Как видно из вышеизложенного, переменные-члены String являются закрытыми финальными, то есть их нельзя изменить после инициализации. Среди этих членов значение имеет особое значение, поскольку это ссылочная переменная, а не реальный объект. value изменяется с помощью Final, а это означает, что Final больше не может указывать на другие объекты массива, поэтому могу ли я изменить массив, на который указывает значение? Например, измените символ в определенной позиции массива на подчеркивание «_». По крайней мере, мы не можем сделать это в обычном коде, который пишем сами, потому что мы вообще не можем получить доступ к этой ссылке на значение, не говоря уже о том, чтобы изменить массив с помощью этой ссылки.
Итак, как мы можем получить доступ к частным членам? Правильно, с помощью отражения можно отразить атрибут value в объекте String, а затем изменить структуру массива через полученную ссылку на значение. Вот пример кода:
public static void testReflection() throws Exception { //Создаем строку «Hello World» и присваиваем ее ссылке s String s = «Hello World»; World //Получаем поле значения в классе String Field valueFieldOfString = String.class.getDeclaredField("value"); //Изменяем права доступа к атрибуту значения valueFieldOfString.setAccessible(true); //Получаем значение атрибута value объекта s char[] value = (char[]) valueFieldOfString.get(s); //Изменяем пятый символ в массиве, на который ссылается значение value[5] = '_' ; System.out.println("s = " + s); //Hello_World }Результат печати:
s = Привет, мир s = Привет_мир
В этом процессе s всегда ссылается на один и тот же объект String, но до и после отражения объект String изменяется. Другими словами, так называемый «неизменяемый» объект может быть изменен посредством отражения. Но обычно мы этого не делаем. Этот пример отражения также может иллюстрировать проблему: если состояние объекта и других объектов, из которых он состоит, может измениться, то этот объект, вероятно, не является неизменяемым объектом. Например, объект Car объединяется с объектом Wheel. Хотя объект Wheel объявлен закрытым финалом, внутреннее состояние объекта Wheel может измениться, поэтому невозможно гарантировать, что объект Car будет неизменяемым.