Чому String незмінний на Java?


78

Я не міг зрозуміти причину цього. Я завжди використовую клас String, як і інші розробники, але коли я змінюю його значення, створюється новий екземпляр String.

Що може бути причиною незмінності класу String на Java?

Я знаю, що є деякі альтернативи, такі як StringBuffer або StringBuilder. Це просто цікавість.


20
Технічно це не дублікат, але Ерік Ліпперт дає чудову відповідь на це питання тут: programmers.stackexchange.com/a/190913/33843
Heinzi

Відповіді:


105

Паралельність

Java визначалася з самого початку з міркувань одночасності. Як часто згадувалося, спільні мутації є проблематичними. Одна річ може змінити іншу за тильною стороною іншої нитки, не знаючи про це.

Є безліч багатопотокових помилок C ++, які з'явилися через спільну рядок - де один модуль вважав, що це безпечно змінити, коли інший модуль у коді врятував на нього вказівник і очікував, що він залишиться таким же.

"Рішення" цього полягає в тому, що кожен клас робить захисну копію змінних об'єктів, які передаються йому. Для змінних рядків це копія O (n). Для незмінних рядків створення копії є O (1), оскільки це не копія, це той самий об'єкт, який не може змінити.

У багатопотоковому середовищі незмінні предмети завжди можна безпечно ділити між собою. Це призводить до загального скорочення використання пам'яті та покращує кешування пам'яті.

Безпека

Багато разів рядки передаються навколо як аргументи конструкторам - мережеві з'єднання та протоколи - це два, які найлегше приходять в голову. Можливість змінити це у невизначений час пізніше при виконанні може призвести до проблем із безпекою (функція вважала, що вона підключається до однієї машини, але була перенаправлена ​​на іншу, але все в об'єкті виглядає так, як це підключено до першої ... його навіть однаковий рядок).

Java дозволяє використовувати відображення - і параметри для цього - це рядки. Небезпека переходу рядка, який може бути модифікований шляхом до іншого відображення. Це дуже погано.

Ключі до хешу

Хеш-таблиця - одна з найбільш використовуваних структур даних. Клавіші структури даних дуже часто є рядками. Наявність незмінних рядків означає, що (як зазначено вище) хеш-таблиці не потрібно робити копію хеш-ключа кожен раз. Якби рядки були змінними, а хеш-таблиця цього не зробила, можна було б щось змінити хеш-ключ на відстані.

Те, як працює об’єкт в Java, полягає в тому, що все має хеш-ключ (доступ до якого здійснюється методом hashCode ()). Мати незмінний рядок означає, що хеш-код може бути кешований. Зважаючи на те, як часто Strings використовуються як ключі до хешу, це забезпечує значне підвищення продуктивності (а не щоразу перераховувати хеш-код).

Підрядки

Маючи String бути незмінним, масив символів, що підтримує структуру даних, також є незмінним. Це дозволяє зробити певні оптимізації щодо substringметоду, який слід виконати (вони не обов'язково робляться - це також вводить можливість деяких витоків пам'яті).

Якщо ти зробиш:

String foo = "smiles";
String bar = foo.substring(1,5);

Значення bar"миля". Однак обидва fooі barможуть бути підкріплені одним і тим же масивом символів, зменшуючи інстанціювання більшої кількості символьних масивів або копіюючи її - просто використовуючи різні початкові та кінцеві точки в рядку.

foo | | (0, 6)
    vv
    посміхається
     ^ ^
бар | | (1, 5)

Тепер недоліком цього (витоку пам’яті) є те, що якби у одного рядка було довжиною 1 кб і було взято підрядку першого та другого символу, він також підтримувався б довгим символом масиву 1 к. Цей масив залишиться в пам'яті, навіть якщо початковий рядок, який мав значення для всього масиву символів, був зібраний сміттям.

Це можна побачити в String від JDK 6b14 (наступний код - з джерела GPL v2 і використовується як приклад)

   public String(char value[], int offset, int count) {
       if (offset < 0) {
           throw new StringIndexOutOfBoundsException(offset);
       }
       if (count < 0) {
           throw new StringIndexOutOfBoundsException(count);
       }
       // Note: offset or count might be near -1>>>1.
       if (offset > value.length - count) {
           throw new StringIndexOutOfBoundsException(offset + count);
       }
       this.offset = 0;
       this.count = count;
       this.value = Arrays.copyOfRange(value, offset, offset+count);
   }

   // Package private constructor which shares value array for speed.
   String(int offset, int count, char value[]) {
       this.value = value;
       this.offset = offset;
       this.count = count;
   }

   public String substring(int beginIndex, int endIndex) {
       if (beginIndex < 0) {
           throw new StringIndexOutOfBoundsException(beginIndex);
       }
       if (endIndex > count) {
           throw new StringIndexOutOfBoundsException(endIndex);
       }
       if (beginIndex > endIndex) {
           throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
       }
       return ((beginIndex == 0) && (endIndex == count)) ? this :
           new String(offset + beginIndex, endIndex - beginIndex, value);
   }

Зверніть увагу, як підрядка використовує String-конструктор на рівні пакету, який не передбачає жодної копіювання масиву і був би набагато швидшим (за рахунок можливо збереження навколо деяких великих масивів - хоча і не дублювання великих масивів).

Зверніть увагу, що наведений вище код призначений для Java 1.6. Спосіб реалізації конструктора підрядків був змінений з Java 1.7, як це зафіксовано у розділі Зміни в рядку внутрішнього представлення, зробленому на Java 1.7.0_06 - проблема, пов’язана з витоком пам'яті, про який я згадував вище. Напевно, Java не сприймалася як мова з великою кількістю маніпуляцій з String, тому підвищення продуктивності для підрядків було гарною справою. Тепер, з величезними документами XML, що зберігаються у рядках, які ніколи не збираються, це стає проблемою ... і, отже, зміна на Stringне використання того самого базового масиву з підрядком, щоб швидше збирати масив символів швидше.

Не зловживайте стеком

Один може передати значення рядка навколо замість посилання на непорушну рядок , щоб уникнути проблем з мінливістю. Однак з великими рядками передача цього на стек буде ... образливою для системи (розміщення цілих документів XML як рядків у стеку, а потім їх зняття або продовження передачі їх разом ...).

Можливість дедуплікації

Зрозуміло, це не було початковою мотивацією того, чому струни повинні бути незмінні, але коли дивиться на раціональне, чому непорушні струни - це добра річ, це, безумовно, щось, що слід враховувати.

Кожен, хто трохи працював зі Strings, знає, що вони можуть висмоктувати пам'ять. Це особливо актуально, коли ви робите такі речі, як витягування даних із баз даних, які тримаються на деякий час. Багато разів з цими струнами вони повторюються один і той же рядок (один раз для кожного ряду).

Наразі багато масштабних програм Java перебувають у вузькому місці в пам'яті. Виміри показали, що приблизно 25% живих даних Java купи в цих типах програм споживаються об'єктами String. Крім того, приблизно половина цих об'єктів String - це дублікати, де duplicates означає string1.equals (string2). Наявність дублікатів об'єктів String на купі - це, по суті, лише витрата пам'яті. ...

З оновленням Java 8, для вирішення цього питання реалізується JEP 192 (мотивація, цитована вище). Не вникаючи в подробиці того, як працює дедуплікація рядків, важливо, щоб самі рядки були непорушними. Ви не можете видалити копію StringBuilders, оскільки вони можуть змінюватися, і ви не хочете, щоб хтось міняв щось під вами. Незмінні рядки (пов’язані з цим пулом String) означають, що ви можете пройти, і якщо ви знайдете два однакових рядка, ви можете вказати одну посилання рядка на іншу і дозволити збору сміття споживати щойно невикористаний.

Інші мови

Завдання C (яке передує Java) має NSStringі NSMutableString.

C # і .NET зробили однаковий вибір дизайну, коли рядки за замовчуванням незмінні.

Струни Луа також непорушні.

Пітон також.

Історично, Lisp, Scheme, Smalltalk усі інтернують рядок і, таким чином, вважають його незмінним. Більш сучасні динамічні мови часто використовують рядки певним чином, що вимагає, щоб вони були незмінні (це може бути не рядком , але воно незмінне).

Висновок

Ці міркування щодо дизайну були зроблені знову і знову на багатьох мовах. Це загальний консенсус, що незмінні рядки, при всій їх незграбності, кращі за альтернативи та призводять до кращого коду (менше помилок) та швидшого виконання файлів загалом.


3
Java пропонує змінні та незмінні рядки. Ця відповідь детально описує деякі переваги продуктивності, які можна зробити на незмінних рядках, та деякі причини, з яких можна вибрати незмінні дані; але не обговорюється, чому незмінна версія є типовою версією.
Біллі ONeal

3
@BillyONeal: безпечна за замовчуванням і незахищена альтернатива майже завжди призводить до більш захищених систем, ніж протилежний підхід.
Йоахім Зауер

4
@BillyONeal Якби незмінний не був типовим, питання одночасності, безпеки та хешів були б більш звичними. Мовні дизайнери вирішили (частково у відповідь на C) створити мову, де встановлені типові параметри, щоб спробувати запобігти появі ряду поширених помилок, щоб спробувати підвищити ефективність програміста (більше не потрібно турбуватися про ці помилки). Більше помилок (очевидних та прихованих) із незмінними рядками, ніж із змінними.

@Joachim: Я не претендую на інше.
Біллі ONeal

1
Технічно у Common Lisp є змінні рядки для операцій, що нагадують рядок, та символи з незмінними іменами для незмінних ідентифікаторів.
Vatine

21

Причини, які я можу згадати:

  1. Засіб Stol Pool без зміни рядка незмінне взагалі неможливо, оскільки у випадку об'єднання рядків на один об'єкт / буквально, наприклад, на "XYZ" буде посилатися багато посилальних змінних, тому, якщо будь-яка з них змінить значення, на інших автоматично впливатиме .

  2. String широко використовується як параметр для багатьох класів Java, наприклад, для відкриття мережевого з'єднання, для відкриття підключення до бази даних, відкриття файлів. Якщо String не є непорушним, це може призвести до серйозної загрози безпеці.

  3. Незмінюваність дозволяє String кешувати свій хеш-код.

  4. Це робить його безпечним для ниток.


7

1) Струнний басейн

Дизайнер Java знає, що String буде найпоширенішим типом даних у всіх видах програм Java, і тому вони хотіли оптимізувати з самого початку. Одним із ключових кроків у цьому напрямку була ідея зберігання рядків Stral у пулі String. Мета полягала в тому, щоб зменшити тимчасовий об'єкт String шляхом їх спільного використання, а для того, щоб ділитися ними, вони повинні бути з класу Immutable. Не можна ділитися об'єктом, що змінюється, двома сторонами, які невідомі одна одній. Візьмемо гіпотетичний приклад, коли дві опорні змінні вказують на той самий об'єкт String:

String s1 = "Java";
String s2 = "Java";

Тепер, якщо s1 змінює об'єкт з "Java" на "C ++", контрольна змінна також отримала значення s2 = "C ++", про яке вона навіть не знає. Зробивши String незмінним, цей спільний доступ до Stral Literal був можливий. Коротше кажучи, ключову ідею String пулу неможливо реалізувати, не зробивши String остаточним чи незмінним у Java.

2) Безпека

У Java є чітка мета щодо забезпечення безпечного середовища на кожному рівні сервісу, а String є критично важливим у всіх цих питаннях безпеки. String широко використовується як параметр для багатьох класів Java, наприклад, для відкриття мережевого з'єднання, ви можете передавати хост і порт як String, для читання файлів на Java ви можете пропустити шлях до файлів і каталогів як String, а також для відкриття з'єднання з базою даних. передайте URL-адресу бази даних як String. Якщо String не був непорушним, користувач, можливо, надав доступ до певного файлу в системі, але після автентифікації він може змінити PATH на щось інше, це може спричинити серйозні проблеми з безпекою. Так само під час підключення до бази даних або будь-якої іншої машини в мережі мутація значення String може створювати загрозу безпеці. Змінні рядки також можуть спричинити проблеми безпеки в Reflection,

3) Використання рядка в механізмі завантаження класу

Ще одна причина для того, щоб зробити String остаточним або незмінним, зумовлена ​​тим, що він активно використовувався в механізмі завантаження класів. Оскільки String не був незмінним, зловмисник може скористатися цим фактом і просити завантажити стандартні Java-класи, наприклад, java.io.Reader можна змінити на шкідливий клас com.unknown.DataStolenReader. Зберігаючи String остаточним та незмінним, ми можемо принаймні бути впевненими, що JVM завантажує правильні класи.

4) Багатопоточні переваги

Оскільки Concurrency and Multi-threadading було ключовою пропозицією Java, було багато сенсу думати про безпеку потоків об'єктів String. Оскільки очікувалося, що String буде широко використовуватися, що робить його Immutable означає відсутність зовнішньої синхронізації, означає набагато більш чистий код, що включає обмін String між кількома потоками. Ця єдина функція значно спрощує кодування, заплутаність та кодування, схильну до помилок. Оскільки String є незмінним, і ми просто ділимось між ними потоками, це призводить до більш читабельного коду.

5) Оптимізація та продуктивність

Тепер, коли ви робите клас Незмінний, ви заздалегідь знаєте, що цей клас не збирається змінюватись колись створений. Це гарантує відкритий шлях для багатьох оптимізацій продуктивності, наприклад кешування. Сам струн знає, що я не збираюсь змінювати, тому String кешує свій хеш-код. Він навіть ліниво обчислює хеш-код і колись створений, просто кешируй його. У простому світі, коли ви вперше викликаєте метод hashCode () будь-якого об'єкта String, він обчислює хеш-код і всі наступні виклики до hashCode () повертає вже обчислене, кешоване значення. Це призводить до хорошого підвищення продуктивності, враховуючи, що String широко використовується в картах на основі хешу, наприклад, Hashtable і HashMap. Кешування хеш-коду було неможливим, не зробивши його непорушним та остаточним, оскільки це залежить від вмісту самого String.


5

Віртуальна машина Java виконує кілька оптимізацій щодо струнних операцій, які не могли бути виконані інакше. Наприклад, якщо у вас був рядок зі значенням "Mississippi" і ви призначили "Mississippi" .substring (0, 4) в інший рядок, наскільки ви знаєте, була зроблена копія з перших чотирьох символів, щоб зробити "Miss" . Чого ви не знаєте, це те, що обидва поділяють однакову початкову рядок "Міссісіпі", один з яких є власником, а другий є посиланням на цей рядок з позиції 0 до 4. (Посилання на власника не дозволяє власникові збирати сміттєзбірник, коли власник виходить за межі поля)

Це тривіально для рядка, розміром якого є "Міссісіпі", але з більшими рядками та декількома операціями, не потрібно копіювати рядок - це велика економія часу! Якщо рядки були змінними, ви цього не можете зробити, оскільки зміна оригіналу вплине і на "копії" підрядки.

Крім того, як зазначає Дональ, перевага була б значно обтяжена його недоліком. Уявіть, що ви пишете програму, яка залежить від бібліотеки, і ви використовуєте функцію, яка повертає рядок. Як ви могли бути впевнені, що це значення залишиться незмінним? Щоб цього не сталося, вам завжди доведеться виготовити копію.

Що робити, якщо у вас є два потоки, які спільно використовують один рядок? Ви б не хотіли читати рядок, який наразі переписується іншою темою, чи не так? Отже, String повинен був бути безпечним для потоків, що, будучи загальним класом, який він є, зробить практично кожну програму Java набагато повільнішою. В іншому випадку вам доведеться зробити копію для кожного потоку, який вимагає цього рядка, або вам доведеться помістити код за допомогою цього рядка в блок синхронізації, обидва вони лише сповільнюють вашу програму.

З усіх цих причин це було одним із ранніх рішень, прийнятих для Java, щоб відмежуватися від C ++.


Теоретично можна виконати багатошарове управління буфером, яке дозволяє копіювати на мутацію, якщо спільне, але це дуже важко, щоб зробити роботу ефективно в багатопотоковому середовищі.
Стипендіати Дональ

@DonalFellows Я просто припустив, що оскільки віртуальна машина Java не написана на Java (очевидно), вона управляється внутрішньо за допомогою спільних покажчиків або чогось подібного.
Ніл

5

Причина незмінності рядка походить від узгодженості з іншими примітивними типами в мові. Якщо у вас є intзначення 42, яке ви додаєте до нього значення 1, ви не змінюєте 42. Ви отримуєте нове значення 43, яке абсолютно не пов'язане з початковими значеннями. Мутація примітивів, окрім струни, не має понятійного сенсу; і тому такі програми, які розглядають рядки як незмінні, часто легше міркувати і розуміти.

Більше того, Java дійсно надає як змінні, так і незмінні рядки, як ви бачите StringBuilder; насправді, лише за замовчуванням є незмінний рядок. Якщо ви хочете передати посилання StringBuilderвсюди, вам це дуже приємно. Java використовує для цих понять окремі типи ( Stringі StringBuilder), оскільки не має підтримки для вираження змін або їх відсутності у своїй системі типів. У мовах, які підтримують незмінність у своїх системах типів (наприклад, C ++ const), часто існує один тип рядка, який служить обом цілям.

Так, наявність непорядних рядків дозволяє реалізувати деякі оптимізації, характерні для незмінних рядків, таких як інтернування, і дозволяє передавати посилання рядків навколо без синхронізації по потоках. Однак це плутає механізм із наміченою метою мови з простою та послідовною системою типів. Я порівнюю це з тим, як всі думають про вивезення сміття неправильно; збирання сміття - це не «рекультивація невикористаної пам’яті»; це "імітація комп'ютера з необмеженою пам'яттю" . Обговорені оптимізації продуктивності - це те, що робиться для того, щоб мета незмінних рядків була ефективною на реальних машинах; не причина в тому, щоб такі рядки були непорушними в першу чергу.


@ Billy-Oneal .. Щодо "Якщо у вас є int, що містить значення 42, і ви додаєте до нього значення 1, ви не змінюєте 42. Ви отримуєте нове значення 43, яке абсолютно не пов'язане з початковим значення ". Ви впевнені в цьому?
Шаміт Верма

@Shamit: Так, я впевнений. Додавання 1 до 42 результатів у 43. Це не означає, що число 42 означає те саме, що і число 43.
Біллі ONeal

@Shamit: Так само ви не можете зробити щось на кшталт 43 = 6і очікуєте, що число 43 означатиме те саме, що і число 6.
Біллі ONeal

int i = 42; i = i + 1; цей код буде зберігати 42 в пам'яті, а потім змінює значення в тому самому місці на 43. Тож фактично змінна "i" набуває нового значення 43.
Shamit Verma

@Shamit: У цьому випадку ви мутували i, а не 42. Поміркуйте string s = "Hello "; s += "World";. Ви змінили значення змінної s. Але рядки "Hello ", "World"і "Hello World"незмінні.
Біллі ONeal

4

Незмінність означає, що константи, утримувані класами, якими ви не володієте, не можуть бути змінені. Класи, якими ви не володієте, включають ті, які лежать в основі реалізації Java, а рядки, які не слід змінювати, включають такі речі, як маркери безпеки, адреси служб тощо. Ви дійсно не повинні мати змогу змінювати ці види речей (і це застосовується подвійно при роботі в пісочному режимі).

Якщо String не був незмінним, кожного разу, коли ви отримували його з якогось контексту, який не хотів, щоб зміст рядка змінювався під його ногами, вам доведеться робити копію «на всякий випадок». Це стає дуже дорогим.


4
Цей точно такий же аргумент стосується будь-якого типу, а не лише до String. Але, наприклад, Arrays все ж є змінними. Отже, чому Strings незмінні та Arrays ні. І якщо незмінність настільки важлива, то чому Java робить так важко створювати та працювати з незмінними об'єктами?
Йорг W Міттаг

1
@ JörgWMittag: Я припускаю, що це в основному питання про те, наскільки радикальними вони хотіли бути. Мати непорушну струну було досить радикально, ще на Яві 1,0 дні. Маючи (в першу чергу або навіть виключно) незмінну систему колекціонування, це могло бути занадто радикальним для широкого використання мови.
Йоахім Зауер

Зробити ефективну систему незмінних колекцій досить складно зробити виконавцем, виступаючи як хтось, хто написав таку річ (але не на Java). Я також повністю бажаю, щоб у мене були незмінні масиви; це врятувало б мені зовсім небагато роботи.
Дональні стипендіати

@DonalFellows: pcollections має на меті зробити саме це (проте ніколи не використовував його сам).
Йоахім Зауер

3
@ JörgWMittag: Є люди (зазвичай з суто функціональної точки зору), які стверджують, що всі типи повинні бути незмінні. Так само я думаю, що якщо ви додасте всі проблеми, які стосуються роботи зі змінним станом паралельно та одночасно програмним забезпеченням, ви можете погодитися, що працювати з незмінними об'єктами часто набагато простіше, ніж із змінними.
Стівен Еверс

2

Уявіть систему, де ви приймаєте деякі дані, перевіряєте їх правильність і потім передаєте їх (наприклад, для зберігання в БД).

Якщо припустити, що дані є a, Stringі вони повинні бути принаймні 5 символів. Ваш метод виглядає приблизно так:

public void handle(String input) {
  if (input.length() < 5) {
    throw new IllegalArgumentException();
  }
  storeInDatabase(input);
}

Тепер ми можемо домовитись, що коли storeInDatabaseбуде закликано сюди, inputволя відповідатиме вимозі. Але якби вони Stringбули змінними, то абонент може змінити inputоб'єкт (з іншого потоку) відразу після того, як він був перевірений і до того, як він буде збережений у базі даних . Для цього потрібні хороші терміни, і, ймовірно, не буде виходити кожен раз, але іноді він зможе змусити вас зберігати недійсні значення в базі даних.

Незмінні типи даних є дуже простим рішенням цієї проблеми (і безлічі пов'язаних з цим) проблем: щоразу, коли ви перевіряєте якесь значення, ви можете залежати від того, що перевірений стан все-таки справдиться.


Дякую за пояснення. Що робити, якщо я називаю такий метод обробки; ручка (новий рядок (вхід + "наберлан")). Я думаю, що я можу зберігати недійсні значення в db, як це.
yfklon

1
@blank: добре, так як inputв handleметоді вже не є надто довго (незалежно від того , що оригінал input є), це було б просто викинути виняток. Ви створюєте новий вхід перед тим, як викликати метод. Це не проблема.
Йоахім Зауер

0

Взагалі, ви зіткнетеся зі значеннями та типовими типами . З типом значення вам не байдуже об’єкт, який його представляє, ви піклуєтесь про значення. Якщо я дам вам значення, ви очікуєте, що це значення залишиться таким же. Ви не хочете, щоб це змінилося раптово. Число 5 - це значення. Ви не очікуєте, що він раптом зміниться до 6. Рядок "Привіт" - це значення. Ви не очікуєте, що він раптово зміниться на "P *** вимкнено".

Що стосується типів посилань, ви дбаєте про об'єкт, і очікуєте, що він зміниться. Наприклад, ви часто будете очікувати зміни масиву. Якщо я даю вам масив, а ви хочете зберегти його таким, яким він є, вам або доведеться довіряти мені, щоб не змінювати його, або ви робите його копію.

З класом рядків Java дизайнерам довелося прийняти рішення: чи краще, якщо рядки поводяться як тип значення, чи вони повинні поводитись як еталонний тип? У випадку рядків Java було прийнято рішення про те, що вони повинні бути типовими значеннями, а значить, оскільки вони є об'єктами, вони повинні бути незмінними об'єктами.

Могло бути прийняте протилежне рішення, але, на мою думку, це призвело б до багатьох головних болів. Як сказано в інших місцях, багато мов прийняли те саме рішення і дійшли одного і того ж висновку. Виняток становить C ++, який має один клас рядків, і рядки можуть бути постійними або непостійними, але в C ++, на відміну від Java, параметри об'єкта можуть передаватися як значення, а не як посилання.


0

Я дуже здивований, що ніхто цього не вказав.

Відповідь: Це не принесло б вам значної користі, навіть якби воно було зміненим. Це вам не принесе користі, оскільки це спричинить додаткові проблеми. Розберемо два найпоширеніші випадки мутації:

Зміна одного символу рядка

Оскільки кожен символ у рядку Java займає 2 або 4 байти, запитайте себе, чи отримаєте ви щось, якби ви могли мутувати існуючу копію?

У сценарії ви замінюєте двобайтовий символ на 4 байти (або навпаки), ви повинні змістити решту частину рядка на 2 байти вліво або вправо. Що не так відрізняється від копіювання всього рядка з обчислювальної точки зору.

Це також дійсно нерегулярна поведінка, яка, як правило, небажана. Уявіть, що хтось тестує програму з англійським текстом, і коли заявка буде прийнята для іноземних країн, таких як Китай, все це починає дивно працювати.

Додавання чергового рядка (або символу) до існуючого

Якщо у вас є два довільних рядки, вони сидять у двох різних місцях пам'яті. Якщо ви хочете змінити перший, додавши другий, ви не можете просто вимагати додаткової пам’яті в кінці першого рядка, оскільки він, ймовірно, вже зайнятий.

Вам потрібно скопіювати об'єднаний рядок у зовсім нове місце, яке точно таке, як якщо б обидва рядки були незмінні.

Якщо ви хочете робити додатки ефективно, ви можете скористатися StringBuilder, що залишає досить великий простір в кінці рядка, саме для цього можливе майбутнє додавання.


-2
  1. вони дорогі, а тримати їх непорушними дозволяє такі речі, як підрядки, що обмінюються байтовим масивом головного рядка. (прискорення швидкості також не потребує створення нового байтового масиву та копіювання)

  2. безпека - не хотілося б, щоб ваш пакунок або код класу були перейменовані

    [видалено старий 3 дивився на StringBuilder src - він не ділиться пам'яттю з рядком (до зміни) Я думаю, що це було в 1.3 або 1.4]

  3. кеш-хеш-код

  4. для мутантних рядків використовуйте SB (builder або буфер за потребою)


2
1. Звичайно, є штраф не в змозі зруйнувати більші частини струни, якщо це станеться. Стажування не безкоштовне; хоча це покращує ефективність для багатьох реальних програм. 2. Можуть легко існувати "string" та "ImmutableString", які могли б задовольнити цю вимогу. 3. Я не впевнений, що я розумію, що ...
Біллі ONeal

.3. повинен був кешувати хеш-код. Це теж можна зробити за допомогою змінного рядка. @ billy-oneal
tgkprog

-4

Рядки повинні були бути примітивним типом даних на Java. Якби вони були, то рядки за замовчуванням були б змінними, а остаточне ключове слово створювало б незмінні рядки. Змінні рядки є корисними, тому в класах stringbuffer, stringbuilder та charsequence є кілька хаків для змінних рядків.


3
Це не відповідає "чому" аспект того, що саме зараз задається питанням. Крім того, фінал Java не працює таким чином. Змінні рядки - це не хакі, а реальні міркування щодо дизайну, що базуються на найпоширеніших варіантах використання рядків та оптимізаціях, які можна зробити для покращення jvm.

1
Відповідь на "чому" - це неякісне дизайнерське рішення. Три трохи різні способи підтримки змінних рядків - це злом, з яким повинен працювати компілятор / JVM.
CWallach

3
String і StringBuffer були оригіналом. Пізніше додано StringBuilder, визнаючи труднощі дизайну з StringBuffer. Змінні та незмінні рядки, що є різними об'єктами, знаходяться у багатьох мовах, оскільки проектний розгляд робився знову і знову, і було вирішено, що кожен раз є різними об'єктами. C # "Строки незмінні" і чому .NET String є незмінним? , об'єктивна C NSString незмінна, тоді як NSMutableString є змінним. stackoverflow.com/questions/9544182
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.