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


177

Мене в інтерв'ю запитали, чому струна незмінна

Я відповів так:

Коли ми створюємо рядок в Java , як String s1="hello";то об'єкт буде створений в струнної пулу (привіт) і s1 буде вказувати привіт .Тепер якщо знову ми String s2="hello";тоді інший об'єкт не буде створено , але s2 буде вказувати , hello тому що JVM спочатку перевірить якщо той самий об’єкт присутній у пулі рядків чи ні. Якщо він не присутній, тоді створюється лише новий.

Тепер , якщо припустимо , що Java дозволяє рядок мутабельном тоді , якщо ми змінимо s1 на hello worldте s2 значення також буде hello worldтак String Java , незмінна.

Чи може будь-який орган сказати мені, чи моя відповідь правильна чи неправильна ?


46
Чому завжди важко відповісти. Найвірніша відповідь, мабуть, тому, що мовні дизайнери вважали, що це гарна ідея.
Кеппіль

1
дивіться також цю відповідь

3
Ваша відповідь не в суті. C ++ std::stringє змінним, але вони також мають рядок рядків (ну, правильніше, пул масивів символів).
Сіюань Рен

1
@rocking Якщо чесно кажучи, правильно чи ні це залежить від того, як вони це читають. Річ у тім, що у Java може бути пул рядків, оскільки рядки незмінні. Якби вони вирішили зробити рядки змінними, вони б не використовували пуловий рядок; тому, можливо, не точно сказати "рядок рядків, тому незмінні рядки"; це навпаки. У причинах вибору незмінних рядків описані нижче, а рядок пул є робоча стратегію з - за цього. Тим не менш, ваша відповідь неправильна , вона просто не здається повною. Вам доведеться просто почекати і подивитися, що вони кажуть.
Джейсон C

34
Я просто не можу зрозуміти, чому це питання було закрито. Імовірна відповідна відповідь навіть не стосується Java і не стосується основної теми цього питання, яка є "чому". Для мене це один із тих випадків безвідповідальної спільноти, яка діє на питання, про яке вони нічого не знають. Я висунув це для повторного відкриття.
Едвін Далорцо

Відповіді:


163

String є незмінним з кількох причин, ось короткий опис:

  • Безпека : параметри зазвичай представлені як Stringу мережевих підключеннях, URL-адресах підключення до бази даних, іменах користувачів / паролях тощо.
  • Синхронізація та сумісність: робить String незмінним автоматично робить їх потоками безпечними, тим самим вирішуючи проблеми синхронізації.
  • Кешування : коли компілятор оптимізує ваші об'єкти String, він бачить, що якщо два об'єкти мають однакове значення (a = "тест" і b = "тест"), і тому вам потрібен лише один об'єкт рядка (і для a і b, ці два будуть вказують на той самий об’єкт).
  • Завантаження класу : Stringвикористовується як аргументи для завантаження класу. Якщо його можна змінити, це може призвести до завантаження неправильного класу (оскільки об'єкти, що змінюються, змінюють свій стан).

Незважаючи на те, що незмінність Stringлише означає, що ви не можете змінити її за допомогою свого публічного API. Фактично можна обійти звичайний API за допомогою відображення. Відповідь дивіться тут .

У вашому прикладі, якщо він Stringбув змінним, розгляньте наступний приклад:

  String a="stack";
  System.out.println(a);//prints stack
  a.setValue("overflow");
  System.out.println(a);//if mutable it would print overflow

14
Як це могло вплинути на безпеку?
Архит Махешварі

2
Чи може хтось пояснити завантаження класу прикладом, якщо це можливо?
Viraj

6
Що стосується безпеки, якщо мене цікавить зміна параметрів з'єднання, це зрозуміло під час виконання (з налагоджувачем тощо). Щодо завантаження класу, якщо він Stringє змінним, то завантажувач класів буде приймати переданий рядок, робити копію, а не змінювати його копію. Розмірковуючи про проблему з mutable java.lang.Strings, подумайте, як C ++ вирішує цю проблему (оскільки вона має змінний std::strings.
Обмежене спокута

Що стосується безпеки, як можна змінити змінну рядок під час роботи програми?
MasterJoe2

Оскільки String є незмінним, його хеш-код кешується під час створення, і його не потрібно обчислювати заново.
Абдул Алім Шакір

45

Розробники Java вирішують, що рядки незмінні завдяки такому аспекту дизайну, ефективності та безпеці .

Струни дизайну створюються в спеціальній області пам'яті в купі java, відомій як "String Intern pool". Під час створення нового String (не у випадку використання конструктора String () або будь-яких інших функцій String, які внутрішньо використовують конструктор String () для створення нового об'єкта String; конструктор String () завжди створює нову константну строку в пулі, якщо ми не викликайте змінну методу intern () ), вона шукає пул, щоб перевірити, чи існує він вже. Якщо він існує, поверніть посилання на існуючий об'єкт String. Якщо String не є незмінним, зміна String з однією посиланням призведе до неправильного значення для інших посилань.

Відповідно до цієї статті про DZone:

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

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


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

@JonSkeet Ви праві. String s1 = new String ("тест"); оператор створює нову константу рядків у пулі інтернів, якщо ми не викликаємо метод intern () Дякую вам за поглиблення моїх знань про струнний пул інтернів.
Олексій Матвій

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

Substring @JonSkeet (), concat (), substitute () тощо застосовують внутрішньо конструктор String для створення нового рядкового об'єкта. Дякую за те, що ви покращили мою відповідь.
Олексій Матвій

2
@JonSkeet - всі ці відповіді говорять про те, що незмінність покращує "безпеку", але не пояснюйте, як. Всі вони посилаються на невиразну статтю про зону, яка також не допомагає. Відповіді / посилання не пояснюють, як можна змінити змінну рядок під час запуску коду. Не могли б ви пояснити?
MasterJoe2

25

Ми не можемо бути впевнені, що насправді думали дизайнери Java під час проектування, Stringале ми можемо зробити висновки лише про ці причини, виходячи з тих переваг, які ми отримуємо від непорушності рядків, деякі з яких:

1. Існування струнного постійного басейну

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

Щоразу, коли ми створюємо літеральний рядок, JVM вперше бачить, чи є цей літерал вже присутній у постійному пулі чи ні, і якщо він є, нова посилання почне вказувати на той самий об’єкт у SCP.

String a = "Naresh";
String b = "Naresh";
String c = "Naresh";

У наведеному вище прикладі рядки об'єкта зі значенням Nareshотримає створений в SCP тільки один раз , і всі посилання a, b, cбуде вказувати на той самий об'єкт , але що , якщо ми спробуємо внести зміни в aнаприклад a.replace("a", "").

В ідеалі, aмає мати значення , Nreshале b, cмає залишатися незмінним , оскільки в якості кінцевого користувача ми робимо зміни в aтільки. І ми знаємо a, b, cвсі вони вказують на той самий об'єкт , так що якщо ми робимо зміни a, інші повинні також відображати зміни.

Але незмінність рядків рятує нас від цього сценарію, і завдяки незмінюваності об'єкта string рядковий об'єкт Nareshніколи не зміниться. Отже, коли ми робимо будь-яку зміну aзамість зміни об'єкта рядка, NareshJVM створює новий об'єкт, присвоює йому aі потім вносить зміни в цей об'єкт.

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

І тому цим JVM обробляє дуже спеціально і йому надається особлива область пам’яті.

2. Безпека нитки

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

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

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

3. Безпека

У кожній програмі нам потрібно передавати кілька секретів, наприклад, ім’я користувача \ паролі, URL-адреси підключення і взагалі вся ця інформація передається як об'єкт рядка.

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

4. Завантаження класу

Як обговорювалося в « Створення об’єктів через Reflection в Java з Example» , ми можемо використовувати Class.forName("class_name")метод для завантаження класу в пам’ять, який знову викликає інші методи для цього. І навіть JVM використовує ці методи для завантаження класів.

Але якщо ви чітко бачите, що всі ці методи приймають ім'я класу як об'єкт рядка, тому Strings використовуються при завантаженні java-класу, а незмінність забезпечує безпеку, що завантажується правильний клас ClassLoader.

Припустимо, якби String не був би незмінним, і ми намагаємося завантажити, java.lang.Objectякі змінюються org.theft.OurObjectміж ними, і тепер усі наші об'єкти мають поведінку, яку хтось може використовувати до небажаних речей.

5. Кешування HashCode

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

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

Так, String об'єкт кешує свій хеш-код під час створення об’єкта, що робить його чудовим кандидатом для хеш-операцій, оскільки хеш-код не потрібно обчислювати знову, що економить нам час. Ось чому String використовується в основному як HashMapклавіші.

Детальніше про те, чому струна є незмінною та остаточною на Java .


1
Щодо безпеки - Як можна змінити значення змінного рядка в пам'яті? Як інша людина може отримати доступ до наших змінних посилань?
MasterJoe2

Йдеться не про те, як можна отримати доступ до посилань, це те, що якщо хтось має доступ до них? як згадувалося ", якщо String не був би незмінним за своєю природою, це спричинило б серйозну загрозу безпеці програми, оскільки ці значення дозволяється змінювати, а якщо це дозволено, вони можуть бути змінені через неправильно написаний код або будь-яку іншу особу які мають доступ до наших змінних посилань. "
Нареш Джоші

Як тут важливо. Це або можливо отримати доступ до посилань, або ні. Якщо можливо, то можете назвати 1-2 методи *** (тобто як), які можна використовувати для цього? Якщо це неможливо, то пункт про безпеку не застосовується. *** Приклад - Назвіть одну техніку для атаки на БД веб-програми -> SQL Injection. Чи знаєте ви подібні прийоми для нападу на посилання?
MasterJoe2

Як зазначалося, "Це може статися через неправильно написаний код або будь-які зміни, внесені іншою особою, яка має доступ до наших змінних посилань". Наприклад, припустимо, що String є змінним, і ви пишете якийсь метод, який використовує рядок таємницю рядка, і знову ця рядок передається до декількох інших методів між ними, і один із цих методів не написаний вами, і цей метод вніс деякі зміни в цей рядок тепер після виклику всіх цих методів управління повертає ваш метод, і ви знову використовуєте цей рядок, але він був змінений.
Naresh Joshi

2
Будь ласка, розкривайте будь-які приналежності та не використовуйте сайт як спосіб просування вашого сайту шляхом публікації. Див. Як написати гарну відповідь? .
Іветт

21

Найголовніша причина відповідно до цієї статті про DZone:

String Constant Pool ... Якщо рядок є змінною, зміна рядка з однією посиланням призведе до неправильного значення для інших посилань.

Безпека

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

Сподіваюся, це допоможе вам.


@JasonC Я просто хочу знати, чи моя відповідь неправильна чи ні. Я вже брав участь у співбесіді і чекав результату. Якщо відповідь сказав їм, то я буду обраний
гойдаючи

1
Згідно з моїми знаннями, ваша відповідь правильна, але незмінна середня посилання ніколи не змінить місця вказування. Все найкраще для вашого інтерв'ю.
JDGuide

1
Якщо ви приймаєте свою точку №1, то всі об'єкти повинні бути незмінні.
nicomp

Привіт, JDeveloper, я відредагував вашу відповідь, щоб дати належне віднесення до джерела вашої відповіді. Пам’ятайте, що завжди використовуйте блок-лапки для дослівних копій вмісту. Дякую!
NickL

Стаття DZone містить основні помилки щодо роботи пулу Strign. Це лише для констант. Ерго зазначене обґрунтування недійсне.
Маркіз Лорн

4

Я читав цю публікацію Чому String є незмінним або завершальним на Java і припускаю, що наступне може бути найважливішою причиною:

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


1

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

Я б додав ще одне, оскільки Stringце незмінне, це безпечно для багатопотокової передачі, і один екземпляр String можна ділити в різних потоках. Це дозволяє уникнути використання синхронізації для безпеки потоку, рядки неявно thread safe.


0

Клас рядків - FINALце означає, що ви не можете створити жодного класу для його успадкування та змінити основну структуру та зробити Sting змінним.

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

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


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

@Zeeshan: Наведені приклади класів незмінні.
Сіюань Рен

0

Мікросистеми Sun не передаються за допомогою рядка, оскільки рядок може використовуватися для зберігання в якості ключа в колекції карт. StringBuffer є змінним. Це причина, яку не можна використовувати як ключ в об'єкті карти


0

Найважливішою причиною того, що струна стає незмінною на Java, - це розгляд безпеки . Далі буде кешування .

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

Перевірте стенограму інтерв'ю Gosling тут

Зі стратегічної точки зору, вони, як правило, частіше безоплатно. І зазвичай є речі, які ви можете зробити з незмінними, які ви не можете зробити зі змінними речами, наприклад кеш результату. Якщо ви передаєте рядок методу відкриття файлу, або якщо ви передаєте рядок конструктору для мітки в користувальницькому інтерфейсі, у деяких API (наприклад, у багатьох API-програмах Windows) ви передаєте масив символів. Одержувач цього об’єкта дійсно повинен його скопіювати, оскільки вони нічого не знають про термін його зберігання. І вони не знають, що відбувається з об'єктом, чи змінюється він під їхніми ногами.

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

Однією з речей, які змушували Стрингс бути непорушною, була безпека. У вас є метод відкриття файлу. Ви передаєте їй струну. І тоді він робить всілякі перевірки автентичності, перш ніж він обійдеться робити виклик ОС. Якщо вам вдасться зробити щось, що ефективно мутувало String, після перевірки безпеки та перед викликом ОС, тоді бум, ви перебуваєте. Але Strings незмінні, тому така атака не працює. Цей точний приклад - це те, що насправді вимагало, щоб Струни були незмінні


0

Окрім чудових відповідей, хотілося додати ще кілька балів. Як і Strings, Array містить посилання на початок масиву, тому якщо ви створите два масиви arr1і зробите arr2щось подібне, arr2 = arr1воно зробить посилання таким arr2же, як arr1значить зміна значення в одному з них призведе до зміни іншого, наприклад

public class Main {
    public static void main(String[] args) {
        int[] a = {1, 2, 3, 4};
        int[] b = a;
        a[0] = 8;
        b[1] = 7;
        System.out.println("A: " + a[0] + ", B: " + b[0]);
        System.out.println("A: " + a[1] + ", B: " + b[1]);
        //outputs
        //A: 8, B: 8
        //A: 7, B: 7
    }
}

Мало того, що це призведе до помилок у коді, він також може (і буде) використаний зловмисним користувачем. Припустимо, якщо у вас є система, яка змінює пароль адміністратора. Користувачеві потрібно спочатку ввести newPasswordі потім, oldPasswordякщо oldPasswordє те ж, що adminPassі програма змінити пароль на adminPass = newPassword. скажімо, що новий пароль має те саме посилання, що і пароль адміністратора, тому поганий програміст може створити tempзмінну для зберігання пароля адміністратора перед тим, як користувачі вводять дані, якщо значення oldPasswordдорівнюєtemp воно змінить пароль інакшеadminPass = temp. Хтось знаючи, що може легко ввести новий пароль і ніколи не вводити старий пароль і абракадабра, він має адміністраторський доступ. Інша річ, яку я не розумів, коли дізнавався про Strings, чому JVM не створює нову рядок для кожного об'єкта і не має для нього унікального місця в пам'яті, і ви можете просто зробити це, використовуючи new String("str");причину, яку ви не хотіли б завжди використовувати new, тому що це не ефективна пам'ять, а в більшості випадків повільніше читати більше .


0

Якщо HELLOваша рядок , то ви не можете змінити HELLOдо HILLO. Ця властивість називається властивістю незмінності.

Ви можете мати кілька змінних String String до точки HELLO String.

Але якщо HELLO - це char Array, ви можете змінити HELLO на HILLO. Наприклад,

char[] charArr = 'HELLO';
char[1] = 'I'; //you can do this

Відповідь:

Мови програмування мають незмінні змінні даних, щоб їх можна було використовувати як ключі у ключі, значенні пари. Строкові змінні використовуються як ключі / індекси, тому вони незмінні .


-1

З Securityточки зору, ми можемо використовувати цей практичний приклад:

DBCursor makeConnection(String IP,String PORT,String USER,String PASS,String TABLE) {

    // if strings were mutable IP,PORT,USER,PASS can be changed by validate function
    Boolean validated = validate(IP,PORT,USER,PASS);

    // here we are not sure if IP, PORT, USER, PASS changed or not ??
    if (validated) {
         DBConnection conn = doConnection(IP,PORT,USER,PASS);
    }

    // rest of the code goes here ....
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.