Чому клас String оголошено остаточним на Java?


141

З того часу, коли я дізнався, що клас java.lang.Stringна Java оголошено остаточним, мені було цікаво, чому це так. Тоді я не знайшов жодної відповіді, але цей пост: Як створити репліку класу String на Java? нагадав мені мій запит.

Звичайно, String надає всі функції, які мені колись потрібні, і я ніколи не думав про будь-яку операцію, яка потребувала б розширення класу String, але все одно ви ніколи не дізнаєтесь, що комусь може знадобитися!

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


Дякую всім за відповіді, особливо TrueWill, Bruno Reis та Thilo! Я б хотів, щоб я міг вибрати декілька відповідей як найкращу, але, на жаль ...!
Алекс Нтусіас

1
Також розглянемо розповсюдження проектів "О, мені просто потрібно ще кілька утилітних методів на String", які з'являться - всі вони не могли використовувати струни один одного, оскільки вони були іншим класом.
Thorbjørn Ravn Andersen

Дякую за цю відповідь дуже корисно. зараз у нас два факти. Рядок - це заключний клас і його незмінний, оскільки його неможливо змінити, але його можна передати іншому об'єкту. а як щодо: - String a = new String ("test1"); тоді, s = "test2"; Якщо String є об'єктом Final class, то як його можна змінити? Як я можу використовувати модифікований кінцевий об'єкт. Будь ласка, дозвольте мені, якщо я щось неправильно запитав.
Суреш Шарма

Ви можете переглянути цю хорошу статтю .
Анікет Такур

4
Одне, чого ми, на щастя, уникали на Java - це "кожен має свій підклас String з безліччю додаткових методів, і жоден з них не сумісний один з одним".
Thorbjørn Ravn Andersen

Відповіді:


88

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

Однією з переваг незмінних об'єктів є те

Ви можете ділитися дублікатами, вказуючи їх на один екземпляр.

( звідси )

Якщо String не був остаточним, ви можете створити підклас і мати два рядки, схожі на "Strings", але насправді різні.


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

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

4
Оце Так! Ансамблі? Ви не розумієте, як підкласи стосуються незмінності? Буду вдячний за пояснення в чому проблема.
Бруно Рейс

7
@Bruno, re: downvotes: Я вас не порушив, але ви можете додати речення щодо того, як запобігання підкласам застосовує незмінність. Зараз це начебто відповідь на половину.
Тіло

12
@BrunoReis - знайшов приємну статтю, на яку можна покластись, - інтерв'ю з Джеймсом Гослінгом (творцем Java), де він коротко розповідає про цю тему тут . Ось цікавий фрагмент: "Однією з речей, які змушували Strings бути непорушною, була безпека. У вас є метод відкриття файлу. Ви передаєте йому String. І тоді він робить всі види перевірки автентичності, перш ніж дістатися до виконання операційної системи виклик. Якщо вам вдасться зробити щось, що ефективно мутувало String, після перевірки безпеки та перед викликом ОС, тоді бум, ви перебуваєте у ... "
Anurag

60

Це приємна стаття, яка викладає дві причини, про які вже йшлося у вищезазначених відповідях:

  1. Безпека : система може роздавати чутливі біти інформації лише для читання, не переживаючи, що вони будуть змінені
  2. Продуктивність : незмінні дані дуже корисні, щоб зробити речі безпечними.

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


Розширення String зіграло б хаос з рівними та стажистами. JavaDoc каже, що дорівнює:

Порівняє цей рядок із заданим об'єктом. Результат вірний, якщо і тільки якщо аргумент не є нульовим і є об'єктом String, який представляє ту саму послідовність символів, що і цей об'єкт.

Якщо припустити, що java.lang.Stringне було остаточним, а SafeStringможе бути рівним a String, і навпаки; тому що вони представляли б ту саму послідовність символів.

Що буде, якби ви звернулися internдо a SafeString- чи SafeStringперейшов би в рядок рядків JVM? Потім ClassLoaderі всі об'єкти, на SafeStringякі посилаються, були заблоковані на протязі життя СВМ. Ви отримаєте перегони про те, хто може бути першим, хто пройшов стажування послідовності символів - можливо, ваш SafeStringпереможе, може бути String, а може бути SafeStringзавантажений іншим завантажувачем класів (таким чином, іншим класом).

Якщо ви виграли гонку в басейн, це був би справжній сингл, і люди могли отримати доступ до всього вашого середовища (пісочниці) за допомогою роздумів і secretKey.intern().getClass().getClassLoader().

Або JVM міг перекрити цю дірку, переконавшись, що до пулу додано лише конкретні об'єкти String (а не підкласи).

Якщо рівне було реалізовано таким, що SafeString! = StringТоді SafeString.intern! = String.intern, І SafeStringйого потрібно було б додати до пулу. Тоді пул <Class, String>замість цього стане пулом, <String>і все, що вам потрібно буде ввійти в басейн, буде новим завантажувачем класів.


2
Звичайно, причина продуктивності є помилкою: якби String був інтерфейсом, я міг би забезпечити реалізацію, яка працює в моїй програмі краще.
Стефан Еггермонт

28

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

Якби String був змінним чи не остаточним, запит на завантаження "java.io.Writer" міг бути змінений, щоб завантажити "mil.vogoon.DiskErasingWriter"

довідка: Чому String незмінний на Java


15

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

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

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

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


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

1
@Kevin: так. Заключний у класі гарантує відсутність підкласів. Не має нічого спільного з незмінністю.
Тіло

4
Оформлення випускного класу саме по собі не робить його незмінним. Але перетворення остаточного класу гарантує, що ніхто не створює підклас, який порушує незмінність. Можливо, людям, які роблять думку про незмінність, було незрозуміло саме те, що вони мали на увазі, але їх твердження є правильними, коли їх розуміють у контексті.
Джей

Декілька разів назад я читав цю відповідь, і думав, що це відповідь добре, потім я прочитав хеш-код & дорівнює з "Ефективна Java" і зрозумів, що це дуже хороша відповідь. Кожен потребує пояснень, я рекомендую ieam 8 & iteam 9 тієї ж книги.
Абхішек Сінгх

6

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

  • Безпека нитки
  • Безпека
  • Купа, якою керує сама Java (інакше, ніж звичайна купа, тобто сміття, зібране по-різному)
  • Управління пам'яттю

В основному це так, що ви, як програміст, можете бути впевнені, що ваш рядок ніколи не буде змінено. Це також, якщо ви знаєте, як це працює, може покращити управління пам'яттю. Спробуйте створити дві однакові рядки одна за одною, наприклад "привіт". Якщо ви налагодите, ви помітите, що вони мають ідентичні ідентифікатори, це означає, що вони саме ТОЖИХ об’єктів. Це пов’язано з тим, що Java дамо вам це зробити. Це не було б можливо, якби струни були незмінними. Вони можуть мати те саме, що я, і т. Д., Тому що вони ніколи не зміняться. Отже, якщо ви коли-небудь вирішите створити 1 000 000 рядка "привіт", що ви дійсно зробите, це створити 1 000 000 вказівників на "привіт". Окрім того, що будь-яка функція на рядку чи будь-які обгортки з цієї причини призведе до створення іншого об'єкта (знову подивіться на ідентифікатор об'єкта - він зміниться).

Додатково остаточний на Java не обов'язково означає, що об'єкт не може змінитися (він відрізняється, наприклад, від C ++). Це означає, що адреса, на яку він вказує, не може змінитися, але ви все одно можете змінити її властивості та / або атрибути. Тож розуміння різниці між незмінністю та остаточним у деяких випадках може бути справді важливим.

HTH

Список літератури:


1
Я не вірю, що струни йдуть в іншу купу або використовують інше управління пам'яттю. Вони, безумовно, збирають сміття.
Тіло

2
Крім того, підсумкове ключове слово в класі абсолютно відрізняється від кінцевого ключового слова для поля.
Тіло

1
Гаразд, на JVM Sun, струни, які стажуються () ed, можуть перейти в перм-ген, який не входить до купи. Але це однозначно не для всіх струнних або всіх JVM.
Тіло

2
Не всі струни йдуть до тієї області, лише струни, які були інтерновані. Інтернування є автоматичним для буквальних рядків. (@Thilo, ввівши, коли ви надсилали свій коментар).
Кевін Брок

Дякую за цю відповідь дуже корисно. зараз у нас два факти. Рядок - це заключний клас і його незмінний, оскільки його неможливо змінити, але його можна передати іншому об'єкту. а як щодо: - String a = new String ("test1"); тоді, s = "test2"; Якщо String є об'єктом Final class, то як його можна змінити? Як я можу використовувати модифікований кінцевий об'єкт. Будь ласка, дозвольте мені, якщо я щось неправильно запитав.
Суреш Шарма

2

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


3
+1 для "проектування на спадщину важко". До речі, це дуже добре пояснено у «Ефективній Java» Блоха.
sleske

2

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

Якщо коротко, оскільки String є незмінним, ніхто не може змінити його вміст, коли він створений, що гарантує hashCode of String однаковим при декількох викликах.

Якщо ви бачите, Stringклас класу оголошено як

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

і hashcode()функція така -

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

Якщо це вже комп'ютер, просто поверніть значення.


2

Щоб переконатися, що ми не отримаємо кращої реалізації. Звичайно, це повинен був бути інтерфейс.

[редагувати] Ах, отримуючи більш чіткі голоси. Відповідь ідеально серйозна. Мені довелося кілька разів запрограмувати дурну реалізацію рядків, що призвело до серйозних втрат продуктивності та продуктивності


2

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


2

Крім причин, зазначених в інших відповідях (безпека, незмінність, продуктивність), слід зазначити, що Stringмає спеціальну мовну підтримку. Ви можете писати Stringлітерали і є підтримка +оператора. Якщо дозволити програмістам підкласи String, це заохотить хаки, такі як:

class MyComplex extends String { ... }

MyComplex a = new MyComplex("5+3i");
MyComplex b = new MyComplex("7+4i");
MyComplex c = new MyComplex(a + b);   // would work since a and b are strings,
                                      // and a string + a string is a string.

1

Ну, у мене є інша думка, я не впевнений, я прав чи ні, але в Java String це єдиний об'єкт, який можна трактувати як примітивний тип даних, а я маю на увазі, що ми можемо створити об'єкт String як String name = "java " . Тепер, як і інші примітивні типи даних, які копіюють за значенням, а не копіюють за посиланням, String, як очікується, матиме таку саму поведінку, і тому String є остаточним. Ось що мій подумав. Будь ласка, ігноруйте, якщо це абсолютно нелогічно.


1
На мою думку, String ніколи не веде себе як примітивний тип. Буквальний рядок на зразок "java" - це фактично об'єкт класу String (ви можете використовувати на ньому оператор крапки, одразу слідуючи кінцевій цитаті). Таким чином, присвоєння літералу рядкової змінної - це просто призначення посилань на об'єкти, як зазвичай. Відмінність полягає в тому, що клас String має підтримку на рівні мови, вбудовану в компілятор ... перетворюючи речі з подвійних лапок в об'єкти String та оператор +, як згадувалося вище.
Георгі

1

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


1

Скажімо, у вас є Employeeклас, у якому є метод greet. Коли greetметод називається, він просто друкується Hello everyone!. Так що це очікуване поведінка по greetметоду

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

Тепер нехай метод GrumpyEmployeeпідкласу Employeeта переосмислення, greetяк показано нижче.

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

Тепер у наведеному нижче коді подивіться на sayHelloметод. Він приймає Employeeекземпляр як параметр і викликає метод привітання, сподіваючись, що він би сказав, Hello everyone!але те, що ми отримуємо, це Get lost!. Ця зміна поведінки відбувається черезEmployee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

Такої ситуації можна уникнути, якщо Employeeклас був зроблений final. Тепер на вашій уяві залежить кількість хаосу, який може викликати зухвалий програміст, якби StringClass не був оголошений як final.


0

Чи знає JVM, що незмінне? Відповідь "Ні". Постійний пул містить усі незмінні поля, але всі незмінні поля / об'єкти не зберігаються лише в постійному пулі. Тільки ми реалізовуємо його таким чином, щоб досягти незмінності та його особливостей. CustomString можна реалізувати, не роблячи його остаточним за допомогою MarkerInterface, який би забезпечував особливу поведінку Java для його об'єднання, функція все ще чекає!


0

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

Однак питання ОП полягає в тому, чому вона остаточна - чому її не можна продовжити. Деякі з них взяли це на себе, але я погодився б з ОП, що тут існує реальна розрив. Інша мова дозволяє розробникам створювати нові номінальні типи для типу. Наприклад, у Haskell я можу створити такі нові типи, які за часом виконання ідентичні текстові, але забезпечують безпеку прив’язки під час компіляції.

newtype AccountCode = AccountCode Text
newtype FundCode = FundCode Text

Тож я б висунув наступну пропозицію як доповнення до мови Java:

newtype AccountCode of String;
newtype FundCode of String;

AccountCode acctCode = "099876";
FundCode fundCode = "099876";

acctCode.equals(fundCode);  // evaluates to false;
acctCode.toString().equals(fundCode.toString());  // evaluates to true;

acctCode=fundCode;  // compile error
getAccount(fundCode);  // compile error

(Або, можливо, ми могли б почати відмовлятися від Java)


-1

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


Будь ласка, уточніть свою відповідь.
Марко Попович
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.