Почему класс String объявлен финальным в Java? - PullRequest
133 голосов
/ 15 января 2010

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

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

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

Ответы [ 16 ]

86 голосов
/ 15 января 2010

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

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

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

(из здесь ).

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

59 голосов
/ 15 января 2010

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

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

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

<Ч />

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

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

Предполагая, что java.lang.String не является окончательным, SafeString может равняться 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>, и все, что вам потребуется для входа в пул, - это новый загрузчик классов.

25 голосов
/ 21 мая 2011

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

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

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

15 голосов
/ 15 января 2010

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

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

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

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

6 голосов
/ 15 января 2010

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

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

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

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

НТН

Ссылки:

2 голосов
/ 20 августа 2015

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

2 голосов
/ 10 января 2014

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

Помимо причин, упомянутых в других ответах (безопасность, неизменность, производительность), следует отметить, что 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.
2 голосов
/ 15 января 2010

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

1 голос
/ 07 июля 2018

Допустим, у вас есть класс 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 экземпляр в качестве параметра и вызывает метод greet, надеясь, что он скажет 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 Class не был объявлен как final.

...