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


141

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

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

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


Спасибо всем за ответы, особенно TrueWill, Бруно Рейс и Тило! Хотел бы я выбрать более одного ответа как лучший, но, к сожалению ...!
Alex Ntousias

1
Также рассмотрите распространение проектов типа «О, мне просто нужно еще несколько утилитарных методов в String», которые будут всплывать - все они не могут использовать строки друг друга, потому что они представляют собой другой класс.
Торбьёрн Равн Андерсен

Спасибо за этот ответ, он очень полезен. у нас есть два факта. String - это класс Final и его неизменяемый, потому что он не может быть изменен, но может быть отнесен к другому объекту. а как насчет: - String a = new String ("test1"); тогда s = "test2"; Если String является объектом класса Final, как его можно изменить? Как я могу использовать модифицированный конечный объект. Пожалуйста, позвольте мне, если я что-то неправильно спросил.
Суреш Шарма

Вы можете просмотреть эту хорошую статью .
Аникет Такур

4
К счастью, в Java мы избежали одной вещи: «у каждого есть свой подкласс String с множеством дополнительных методов, и ни один из них не совместим друг с другом».
Торбьёрн Равн Андерсен

Ответы:


88

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

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

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

( отсюда ).

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


71
Если нет связи между конечными классами и неизменяемыми объектами, которую я не вижу, я не понимаю, как ваш ответ относится к вопросу.
sepp2k

9
Потому что, если он не окончательный, вы можете передать StringChild какому-либо методу как параметр String, и он может быть изменяемым (из-за изменения состояния дочернего класса).
helios

4
Вот это да! Голосов против? Разве вы не понимаете, как подклассы связаны с неизменяемостью? Буду признателен за объяснение, в чем проблема.
Бруно Рейс

7
@Bruno, re: downvotes: я не отрицал вас, но вы можете добавить предложение о том, как предотвращение подклассов обеспечивает неизменность. Прямо сейчас это своего рода полуответ.
Тило

12
@BrunoReis - нашел интересную статью, на которую вы можете ссылаться, в которой есть интервью с Джеймсом Гослингом (создателем Java), где он кратко рассказывает об этой теме здесь . Вот интересный фрагмент: «Одной из вещей, которая заставляла Strings быть неизменными, была безопасность. У вас есть метод открытия файла. Вы передаете ему String. А затем он выполняет всевозможные проверки аутентификации, прежде чем приступит к выполнению ОС вызов. Если вам удастся сделать что-то, что эффективно изменило строку, после проверки безопасности и до вызова ОС, то бум, вы в ... »
Анураг

60

Это хорошая статья, в которой изложены две причины, уже упомянутые в ответах выше:

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

И это, наверное, самый подробный комментарий в той статье. Это связано с пулом строк в Java и проблемами безопасности. Речь идет о том, как решить, что входит в пул строк. Предполагая, что обе строки равны, если их последовательность символов одинакова, тогда у нас есть условие гонки на то, кто попадет первым, а также проблемы безопасности. Если нет, то пул строк будет содержать избыточные строки, что приведет к потере преимущества его наличия. Просто прочти это для себя, а?


Расширение String нанесло бы ущерб равным и стажерам. JavaDoc говорит, что равно:

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

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

Что произойдет, если вы подадите заявку internна SafeString- войдет ли SafeStringона в пул строк JVM? Затем ClassLoaderобъект и все объекты, на которые SafeStringхранятся ссылки, будут заблокированы на время существования JVM. Вы получите условие гонки о том, кто может быть первым, кто интернирует последовательность символов - может быть, вы 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 предотвращает подклассы, которые могут нарушить эти предположения.

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

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


6
finalна классе не гарантирует неизменяемость. Это просто гарантирует, что инварианты класса (один из которых может быть неизменяемым) не могут быть изменены подклассом.
Кевин Брок,

1
@ Кевин: да. Final для класса гарантирует отсутствие подклассов. Не имеет ничего общего с неизменностью.
Тило

4
Создание класса final само по себе не делает его неизменяемым. Но создание неизменяемого класса final гарантирует, что никто не создаст подкласс, нарушающий неизменность. Возможно, люди, высказавшие мысль о неизменности, не совсем понимали, что именно они имели в виду, но их утверждения верны, если понимать их в контексте.
Джей

Несколько раз назад я прочитал этот ответ и подумал, что это нормальный ответ, затем я прочитал хэш-код и равно из «Эффективной Java» и понял, что это очень хороший ответ. Кому-нибудь нужны объяснения, я рекомендую ieam 8 и iteam 9 той же книги.
Абхишек Сингх

6

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

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

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

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

HTH

Ссылки:


1
Я не верю, что строки попадают в другую кучу или используют другое управление памятью. Они, безусловно, подлежат сборке мусора.
Тило

2
Кроме того, ключевое слово final в классе полностью отличается от ключевого слова final для поля.
Тило

1
Хорошо, на JVM Sun, строки, которые являются intern () ed, могут попасть в perm-gen, который не является частью кучи. Но это определенно не происходит для всех строк или всех JVM.
Тило

2
Не все струны попадают в эту область, только те струны, которые были интернированы. Интернирование происходит автоматически для буквальных строк. (@Thilo, набирает, когда отправлял свой комментарий).
Кевин Брок,

Спасибо за этот ответ, он очень полезен. у нас есть два факта. String - это класс Final и его неизменяемый, потому что он не может быть изменен, но может быть отнесен к другому объекту. а как насчет: - String a = new String ("test1"); тогда s = "test2"; Если String является объектом класса Final, как его можно изменить? Как я могу использовать модифицированный конечный объект. Пожалуйста, позвольте мне, если я что-то неправильно спросил.
Суреш Шарма

2

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


3
+1 за «проектирование для наследования сложно». Кстати, это очень хорошо объяснено в «Эффективной Java» Блоха.
sleske

2

Имея уже много хороших моментов, я хотел бы добавить еще один - одна из причин того, почему String неизменяема в Java, заключается в том, чтобы позволить String кэшировать свой хэш-код , будучи неизменным String в Java кеширует свой хэш-код и не вычисляет каждый раз мы вызываем метод hashcode для String , что делает его очень быстрым в качестве ключа hashmap для использования в hashmap в 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;
}

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


2

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

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


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 следовало бы принять неизменность в качестве принципа. (Не затаив дыхание.)

Однако вопрос OP заключается в том, почему он окончательный - почему его нельзя продлить. Некоторые здесь взяли на себя это, но я согласен с OP, что здесь есть настоящий пробел. Другой язык позволяет разработчикам создавать новые номинальные типы для типа. Например, в 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)


-2

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


Пожалуйста, поясните свой ответ.
Марко Попович
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.