Почему класс String объявлен окончательным в Java?

Когда я узнал, что класс java.lang.String объявлен как final в Java, мне стало интересно, почему это так. Тогда я не нашел ответа, но этот пост: Как создать реплику класса String в Java? напомнил мне мой запрос.

Конечно, String предоставляет всю необходимую мне функциональность, и я никогда не думал о какой-либо операции, которая потребовала бы расширения класса String, но все же вы никогда не узнаете, что кому-то может понадобиться!

Итак, кто-нибудь знает, каковы были намерения дизайнеров, когда они решили сделать его окончательным?


person Alex Ntousias    schedule 15.01.2010    source источник
comment
Спасибо всем за ответы, особенно TrueWill, Бруно Рейсу и Тило! Я хотел бы выбрать более одного ответа как лучший, но, к сожалению...!   -  person Alex Ntousias    schedule 15.01.2010
comment
Также рассмотрите распространение О, мне просто нужно еще несколько полезных методов в проектах String, которые будут всплывать - все из которых не могут использовать строки друг друга, потому что они были другим классом.   -  person Thorbjørn Ravn Andersen    schedule 27.02.2013
comment
Спасибо за этот ответ, он очень полезен. теперь у нас есть два факта. String — это класс Final, и он неизменяем, потому что его нельзя изменить, но можно сослаться на другой объект. а как насчет: - String a = new String(test1); тогда s = test2; Если String является объектом класса Final, то как его можно изменить? Как я могу использовать модифицированный конечный объект. Пожалуйста, позвольте мне, если я неправильно спросил что-нибудь.   -  person Suresh Sharma    schedule 09.04.2013
comment
Вы можете просмотреть эту хорошую статью. .   -  person Aniket Thakur    schedule 10.01.2014
comment
Одна вещь, которую мы, к счастью, избежали в Java, это то, что у каждого есть свой собственный подкласс String с множеством дополнительных методов, и ни один из них не совместим друг с другом.   -  person Thorbjørn Ravn Andersen    schedule 13.04.2015
comment
лучшая ссылка javarevisited.blogspot.com/ 2010/10/   -  person Premraj    schedule 23.07.2015
comment
@Premraj, спасибо за ссылку на ссылку. Но после подробного прочтения этой статьи кажется, что неизменяемый и Final абсолютно одинаковы. Мой вопрос здесь - неизменны, а Final - одно и то же? Если нет, то в упомянутой ссылке явно не говорится о том, «почему String является окончательным?» и пожалуйста, помогите мне понять это.   -  person Deepak    schedule 23.01.2019


Ответы (16)


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

Одним из преимуществ неизменяемых объектов является то, что

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

(из здесь).

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

person Bruno Reis    schedule 15.01.2010
comment
Если нет связи между конечными классами и неизменяемыми объектами, которую я не вижу, я не понимаю, как ваш ответ относится к вопросу. - person sepp2k; 15.01.2010
comment
Потому что, если это не окончательно, вы можете передать StringChild какому-либо методу в качестве параметра String, и он может быть изменчивым (из-за изменения состояния дочернего класса). - person helios; 15.01.2010
comment
Предотвращение подклассов предотвращает изменяемый подкласс. - person Thilo; 15.01.2010
comment
Если бы String и его данные-члены не были окончательными, они могли бы быть переопределены производным классом, добавляющим изменчивость. Хорошо, если члены данных не были также приватными. :) - person shoover; 15.01.2010
comment
И поскольку вы не можете переопределить (будучи окончательными) методы hashCode, intern и equal, которые являются фундаментальными для всей библиотеки классов. - person helios; 15.01.2010
comment
Вау! Понижение? Разве вы не понимаете, как создание подклассов связано с неизменностью? Буду признателен за объяснение в чем проблема. - person Bruno Reis; 15.01.2010
comment
@Bruno, re: downvotes: я не минусовал вас, но вы могли бы добавить предложение о том, как предотвращение подклассов обеспечивает неизменность. Сейчас это своего рода половинчатый ответ. - person Thilo; 15.01.2010
comment
@Thilo: я заметил, что это неправда, поэтому я удалил этот комментарий. - person sepp2k; 15.01.2010
comment
@shoover: частные теги сделают взлом более неудобным, но не невозможным. Подкласс может добавлять свои собственные поля, а затем ссылаться на эти поля в переопределенных версиях функций. Это было бы безумием, но вы могли бы это сделать. Я определенно видел более безумный код. - person Jay; 29.01.2011
comment
@Jay: вау, это старо. Но в любом случае речь не идет о взломе или предотвращении его. Если вы можете запустить процесс на машине, вы потенциально можете прочитать (изменить) любую часть его памяти, включая частные члены объекта внутри виртуальной машины. Это все о разработке программного обеспечения, где мы пытаемся уменьшить сложность, скрывая (инкапсулируя) информацию. Один из способов уменьшить сложность — быть уверенным, что объект никогда не изменит своего состояния — неизменность. - person Bruno Reis; 31.01.2011
comment
@Jay: Итак: приватность вообще не связана с безопасностью, она связана с уменьшением сложности. Например, много раз, когда вы компилируете класс в Scala, некоторые из его защищенных членов могут быть записаны как общедоступные в файле .class! Однако компилятор Scala добавит в класс некоторые метаданные, которые не позволят другим классам Scala получить доступ к этим членам. - person Bruno Reis; 31.01.2011
comment
@Bruno: RE ничего себе, это старо: я видел 15 января и думал, что это было 2 недели назад. Я не смотрел на год. - person Jay; 31.01.2011
comment
@Bruno: Но по делу, да, я тоже не говорил о безопасности, а скорее о действительно глупых методах кодирования. Шувер сделал комментарий, который подразумевал, что если сделать данные закрытыми, подкласс не сможет сделать String изменяемым. Цель моего поста заключалась в том, чтобы приваты усложнили кому-то это, но не сделали невозможным. Я не рекомендую это, наоборот, я говорю, что это было бы действительно плохой идеей. Я не думаю, что мы с тобой не согласны. Я просто прокомментировал детальный момент. - person Jay; 31.01.2011
comment
Нельзя ли решить эту проблему, просто добавив комментарий javadoc к классу String, в котором говорится, что любой класс, расширяющий класс String, ДОЛЖЕН быть неизменным, вместо предотвращения наследования через ключевое слово final? - person Alderath; 02.04.2012
comment
@Alderath: даже если бы вы могли поверить, что каждый разработчик будет строго соблюдать это правило, почему вы думаете, что это было бы полезно? - person Bruno Reis; 03.04.2012
comment
@BrunoReis — нашел хорошую статью, на которую вы можете дать ссылку, в которой есть интервью с Джеймсом Гослингом (создателем Java), где он кратко рассказывает об этой теме здесь. Вот интересный фрагмент: Одной из вещей, которая заставила строки быть неизменяемыми, была безопасность. У вас есть метод открытия файла. Вы передаете ему строку. И затем он выполняет всевозможные проверки подлинности, прежде чем приступить к вызову ОС. Если вам удастся сделать что-то, что эффективно мутировало строку, после проверки безопасности и до вызова ОС, тогда бум, вы в... - person Anurag; 11.06.2013
comment
StringBuilder также является окончательным, поэтому его неизменяемость не обязательно является причиной - person Paolo; 28.04.2014
comment
@Paolo: быть окончательным - это только одно из условий неизменности класса. - person Bruno Reis; 28.04.2014
comment
Но разве недостаточно сделать все поля final, чтобы String вело себя так, как задумано? Насколько я понимаю, поля final не могут быть переопределены в Java. - person shitpoet; 24.02.2019

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

Если бы строка была изменяемой или не окончательной, запрос на загрузку «java.io.Writer» мог бы быть изменен на загрузку «mil.vogoon.DiskErasingWriter».

ссылка: Почему String неизменяем в Java

person Jackob    schedule 21.05.2011

String — это очень важный класс в Java, многие вещи зависят от его работы определенным образом, например, от неизменности.

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

Обратите внимание, что даже сейчас, если вы используете отражение, вы можете сломать строки (изменить их значение или хэш-код). Отражение можно остановить с помощью менеджера безопасности. Если бы String не было final, это мог бы сделать каждый.

Другие классы, которые не объявлены final, позволяют вам определять несколько сломанные подклассы (например, у вас может быть List, который добавляет к неправильной позиции), но, по крайней мере, JVM не зависит от них для своих основных операций.

person Thilo    schedule 15.01.2010
comment
final для класса не гарантирует неизменности. Это просто гарантирует, что инварианты класса (одним из которых может быть неизменность) не могут быть изменены подклассом. - person Kevin Brock; 15.01.2010
comment
@Кевин: да. Final для класса гарантирует отсутствие подклассов. Не имеет ничего общего с неизменностью. - person Thilo; 15.01.2010
comment
Создание класса final само по себе не делает его неизменным. Но создание неизменяемого класса final гарантирует, что никто не создаст подкласс, нарушающий неизменность. Возможно, люди, говорящие о неизменности, не совсем поняли, что они имели в виду, но их утверждения верны, если их понимать в контексте. - person Jay; 29.01.2011
comment
Несколько раз назад я читал этот ответ и думал, что это нормальный ответ, затем я прочитал hashcode & equals из «Эффективной Java» и понял, что это очень хороший ответ. Кому нужны пояснения, рекомендую ieam 8 и iteam 9 той же книги. - person Abhishek Singh; 27.11.2016

Как сказал Бруно, речь идет о неизменности. Речь идет не только о строках, но и о любых обертках, например. Double, Integer, Character и т. д. На это есть множество причин:

  • Безопасность потока
  • Безопасность
  • Куча, которой управляет сама Java (в отличие от обычной кучи, которая собирается мусором другим способом)
  • Управление памятью

В основном это делается для того, чтобы вы, как программист, могли быть уверены, что ваша строка никогда не будет изменена. Это также, если вы знаете, как это работает, может улучшить управление памятью. Попробуйте создать две одинаковые строки друг за другом, например "hello". Вы заметите, если вы отлаживаете, что у них идентичные идентификаторы, это означает, что это ОДИНАКОВЫЕ объекты. Это связано с тем, что Java позволяет вам это делать. Это было бы невозможно, если бы строки были изменяемыми. Они могут иметь то же, что и я, и т. д., потому что они никогда не изменятся. Итак, если вы когда-нибудь решите создать 1 000 000 строк «hello», то на самом деле вы создадите 1 000 000 указателей на «hello». Кроме того, использование любой функции в строке или любых оболочек по этой причине приведет к созданию другого объекта (снова посмотрите на идентификатор объекта - он изменится).

Кроме того, final в Java не обязательно означает, что объект не может изменяться (это отличается, например, от C++). Это означает, что адрес, на который он указывает, не может измениться, но вы можете изменить его свойства и/или атрибуты. Поэтому понимание разницы между неизменяемостью и окончательным в некоторых случаях может быть действительно важным.

HTH

Использованная литература:

person Artur    schedule 15.01.2010
comment
Я не верю, что строки идут в другую кучу или используют другое управление памятью. Они, конечно, сбор мусора. - person Thilo; 15.01.2010
comment
Кроме того, конечное ключевое слово класса полностью отличается от конечного ключевого слова поля. - person Thilo; 15.01.2010
comment
Хорошо, в JVM Sun строки, которые интернированы(), могут попасть в perm-gen, который не является частью кучи. Но это определенно не происходит для всех строк или всех JVM. - person Thilo; 15.01.2010
comment
Не все строки попадают в эту область, только строки, которые были интернированы. Интернирование выполняется автоматически для литеральных строк. (@Thilo, печатая, как вы отправили свой комментарий). - person Kevin Brock; 15.01.2010
comment
Спасибо за этот ответ, он очень полезен. теперь у нас есть два факта. String — это класс Final, и он неизменяем, потому что его нельзя изменить, но можно сослаться на другой объект. а как насчет: - String a = new String(test1); тогда s = test2; Если String является объектом класса Final, то как его можно изменить? Как я могу использовать модифицированный конечный объект. Пожалуйста, позвольте мне, если я неправильно спросил что-нибудь. - person Suresh Sharma; 09.04.2013

Возможно, это было сделано для упрощения реализации. Если вы разрабатываете класс, который будет наследоваться пользователями этого класса, то у вас будет совершенно новый набор вариантов использования, которые нужно учитывать при разработке. Что произойдет, если они сделают то или иное с защитным полем X? Делая это окончательно, они могут сосредоточиться на правильной работе общедоступного интерфейса и убедиться, что он надежный.

person AaronLS    schedule 15.01.2010
comment
+1 за проектирование для наследования сложно. Кстати, это очень хорошо объяснено в Эффективной Java Блоха. - person sleske; 29.04.2010

С уже упомянутым множеством хороших моментов я хотел бы добавить еще один: одна из причин того, почему String неизменяема в Java, заключается в том, чтобы позволить String кэшировать свой хэш-код , будучи неизменяемой String в Java кэширует свой хэш-код и не вычислять каждый раз, когда мы вызываем метод хэш-кода строки, что делает его очень быстрым в качестве ключа хэш-карты для использования в хэш-карте в Java.

Короче говоря, поскольку String является неизменной, никто не может изменить ее содержимое после создания, что гарантирует, что hashCode 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;
}

Если это уже компьютер, просто верните значение.

person Aniket Thakur    schedule 10.01.2014

Чтобы убедиться, что мы не получим лучшую реализацию. Конечно, это должен был быть интерфейс.

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

person Stephan Eggermont    schedule 07.03.2011

Помимо очевидных причин, предложенных в других ответах, одна мысль о том, чтобы сделать класс String final, также может быть связана с накладными расходами на производительность виртуальных методов. Помните, что String — это тяжелый класс, что делает его окончательным, что означает отсутствие подреализации наверняка, означает отсутствие накладных расходов на косвенные вызовы. Конечно, теперь у нас есть такие вещи, как виртуальный вызов и другие, которые всегда делают такую ​​оптимизацию за вас.

person Abhishek Singh    schedule 20.08.2015

В дополнение к причинам, упомянутым в других ответах (безопасность, неизменность, производительность), следует отметить, что 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.
person aioobe    schedule 22.04.2010

Ну, у меня другая мысль, я не уверен, прав я или нет, но в Java String - это единственный объект, который также можно рассматривать как примитивный тип данных, я имею в виду, что мы можем создать объект String как имя строки ="java". Теперь, как и другие примитивные типы данных, которые копируются по значению, а не копируются по ссылке, ожидается, что String будет иметь такое же поведение, поэтому String является окончательным. Вот что я подумал. Пожалуйста, игнорируйте, если это совершенно нелогично.

person webdev    schedule 08.12.2010
comment
На мой взгляд, String никогда не ведет себя как примитивный тип. Строковый литерал, такой как java, на самом деле является объектом класса String (вы можете использовать для него оператор точки сразу после закрывающей кавычки). Таким образом, присваивание литерала строковой переменной — это просто присваивание ссылок на объекты, как обычно. Отличие заключается в том, что класс String имеет поддержку на уровне языка, встроенную в компилятор... превращая вещи в двойные кавычки в объекты String и оператор +, как упоминалось выше. - person Georgie; 18.01.2014

Завершенность строк также защищает их как стандарт. В C++ вы можете создавать подклассы строки, поэтому каждый магазин программирования может иметь свою собственную версию строки. Это приведет к отсутствию сильного стандарта.

person ncmathsadist    schedule 10.12.2012

Допустим, у вас есть класс 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. Теперь дело за вашим воображением, сколько хаоса может вызвать дерзкий программист, если класс String не будет объявлен как final.

person Andy    schedule 06.07.2018

Знает ли JVM, что является неизменным? Ответ: нет. Постоянный пул содержит все неизменяемые поля, но все неизменяемые поля/объекты хранятся не только в постоянном пуле. Только мы реализуем его так, чтобы он добился неизменности и своих свойств. CustomString можно реализовать, не делая его окончательным, используя MarkerInterface, который обеспечит специальное поведение Java для его объединения, функция все еще ожидается!

person hi.nitish    schedule 09.02.2017

Большинство ответов связаны с неизменностью - почему объект типа 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)

person dsmith    schedule 10.10.2017

Если вы создадите строку один раз, она будет считать, что это объект, если вы хотите изменить это, это невозможно, он создаст новый объект.

person Suresh    schedule 24.02.2016
comment
Пожалуйста, уточните свой ответ. - person Marko Popovic; 24.02.2016

person    schedule
comment
Конечно, причина производительности — это заблуждение: если бы String был интерфейсом, я мог бы предоставить реализацию, которая лучше работает в моем приложении. - person Stephan Eggermont; 11.02.2016