java.lang.String: вы уверены, что знаете про строки все?
14.1. Java — Строки и методы класса String
Строки — представляют собой последовательность символов. Строки в Java широко используются и являются объектами.
Платформа Java предоставляет класс строк (class String) для создания и работы со строками.
Содержание
Создание строк
Наиболее простой способ создать строку:
После каждого раза, когда компилятор встречает строковый литерал в коде, он создает строковый объект (String) со значение, в данном случае «Здравствуй, мир!».
Как и в случае с другими объектами, можно создавать строковые объекты, используя ключевое слово new и конструктор. Класс строк имеет одиннадцать конструкторов, которые позволяют обеспечить начальное значение строки, используя различные источники, таких как массив символов.
Пример
Получим следующий результат:
Примечание: Класс строк является неизменяемым, так что как только он будет создан, строковый объект не может быть изменен. Если есть необходимость сделать много изменений в строке символов, следует использовать классы строки буфера (String Buffer) и построитель строки (String Builder Classes).
Длина строки
Методы, используемые для получения информации об объекте, известны как методы доступа. Один из методов доступа, который можно использовать со строками является метод length(), он возвращает количество символов, содержащихся в строковом объекте.
Ниже представлен пример метода length(), который поможет определить длину строки.
Пример
Получим следующий результат:
Объединение строк в Java
Класс строк включает метод для объединения двух строк:
Возвращает новую строку string1, с добавленной строкой string2 в конце. Вы также можете использовать метод concat() со строковыми литералами:
Чаще строки объединяют оператором +:
Давайте посмотрим на следующем примере.
Пример
Получим следующий результат:
Создание формата строк
Мы уже изучали методы printf() и format() для вывода на печать с отформатированными числами. Класс строк в Java обладает эквивалентным методом format(), который возвращает строковый объект, а не потоковый объект (PrintStream).
Использование строкового статического метода format() позволяет создавать строку нужного формата, который можно использовать повторно, в отличие от одноразовых операторов print. Например вместо:
Вы также можете написать:
Методы класса строк
Ниже приведен список методов, поддерживаемых классом строк.
Soft, интернет, безопасность: новости, статьи, советы, работа
Избранное сообщение
Фетісов В. С. Комп’ютерні технології в тестуванні. Навчально-методичний посібник. 2-ге видання, перероблене та доповнене / Мои публикации
В 10-х годах я принимал участие в программе Европейского Союза Tempus “Освітні вимірювання, адаптовані до стандартів ЄС”. В рамк.
четверг, 23 июня 2016 г.
java.lang.String: вы уверены, что знаете про строки все? / Программирование на Java
Свойство Immutable и потокобезопасность
Так как все логические поля класса являются final, то модель памяти java 5.0 гарантирует, что после вызова конструктора все потоки, которые видят ссылку на экземпляр этого класса, увидят и все final поля. Однако до java 5 этого никто не гарантировал, поэтому поток, который данную строку не создавал, вполне мог получить ссылку на инконсистентный (с его точки зрения) объект.
Постойте, но, ведь, значение строки можно поменять через reflection:
- String a = “a” ;
- Field f = String. class .getDeclaredField( “value” );
- f.setAccessible( true );
- f.set(a, new char []< 'b' >);
Таким образом, получается, что строку можно поменять, а другой поток этих изменений и не увидит? Неправда – видимость измененных final полей через reflection тоже гарантируется моделью памяти java 5.0.
Методы, над реализацией которых, мало кто задумывается
Как я уже упоминал вначале, метод substring(int beginIndex, int endIndex) не копирует содержимое массива символов, а использует ссылку на оригинальный массив и выставляет соответствующим образом поля длины строки и сдвига. Таким образом, если вы считали большой файл в строку, потом взяли из него подстроку и удалили ссылку на первый объект, то память у вас нисколько не освободиться. Так же если вы сериализуете полученную подстроку стандартными средствами и передаете её по сети, то опять же ничего хорошего не выйдет, так как будет сериализован весь оригинальный массив и количество передаваемых байтов по сети вас неприятно удивит. Если это ваш случай, то после нахождения подстроки вы можете воспользоваться конструкторомString(String original), который скопирует только реально используемую часть массива. Кстати, если этот конструктор вызывать на стоке длиной равной длине массива символов, то копирования в этом случае происходить не будет, а будет использоваться ссылка на оригинальный массив.
Методы String(byte[] bytes) и getBytes() кодируют и декодируют строку в байты и наоборот, используя кодировку по умолчанию, которую можно переопределить запустив JVM с параметром “Dfile.encoding=”. Если вы кодировку не переопределяете, то используется UTF-8. Если её не присутствует в системе, то – ISO-8859-1. Если нет и её, то JVM завершается с кодом -1. Но когда я пытался выставить “Dfile.encoding=” в java 1.4, то она у меня не подцеплялась. Погуглив немного, я выяснил, что с данной проблемой сталкивался не только я, правда, почему этот параметр не хочет работать, я так и не нашел.
Важно не путать вышеупомянутые методы с String(byte[] ascii, int hibyte), String(byte[] ascii, int hibyte, int offset, int count) и getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin), которые могут некорректно преобразовать символы в байты и обратно.
Переопределение операции “+”
- String a = b + c
- String a = new StringBuilder().append(a).append(b).toString()
Т.е. смысла засорять ваш код и вместо простого знака “+” городить громоздкий и плохо читаемый код из StringBuilder смысла никакого нет. Даже скажу больше, те кто в java 1.4. вместо плюсов писали StringBuffer, то их код в пятой джаве теперь может работать даже медленнее, так как StringBuffer работает не быстрее чем StringBuilder.
Еще в последних версиях JVM появился замечательный оптимизационный флаг -XX:+OptimizeStringConcat , который при вызове метода StringBuilder .toString() не будет копировать внутренний массив StringBuilder в новый String, а будет использовать тот же массив, таким образом операция конкатенации будет проходить вообще без копирования массивов.
Однако стоить заметить, что в циклах JVM вам может и не заменить операцию “+” на StringBuilder. Вернее она может заменить её на создание нового StringBuilder на каждом шаге цикла, т.е. код
Java Specialist
Блог о памяти, сборщике мусора, многопоточности и производительности в java
Содержание
понедельник, 16 мая 2011 г.
java.lang.String: вы уверены, что знаете про строки все?
Казалось бы, что может быть непонятно о таком часто используемом объекте как строки? Я тоже так думал долгие годы, однако, если почитать документацию и копнуть поглубже о некоторых свойствах строк, то можно найти много интересного. Например, я только недавно узнал, что время операции intern зависит от размера пула, что, на самом деле, созданную строку можно поменять стандартными средствами java, а в версии JVM 1.4 использование String, строго говоря, не является потокобезопасным.
Класс java.lang.String состоит из трех final полей: массив символов, длина строки и сдвиг в массиве, с помощью которого реализуется метод substring. Так же в этом классе есть поле, в котором хранится посчитанный хэшкод, после первого вызова метода hashCode.
Свойство Immutable и потокобезопасность
Так как все логические поля класса являются final, то модель памяти java 5.0 гарантирует, что после вызова конструктора все потоки, которые видят ссылку на экземпляр этого класса, увидят и все final поля. Однако до java 5 этого никто не гарантировал, поэтому поток, который данную строку не создавал, вполне мог получить ссылку на инконсистентный (с его точки зрения) объект.
Постойте, но, ведь, значение строки можно поменять через reflection:
Таким образом, получается, что строку можно поменять, а другой поток этих изменений и не увидит? Неправда – видимость измененных final полей через reflection тоже гарантируется моделью памяти java 5.0.
Методы, над реализацией которых, мало кто задумывается
Как я уже упоминал вначале, метод substring(int beginIndex, int endIndex) не копирует содержимое массива символов, а использует ссылку на оригинальный массив и выставляет соответствующим образом поля длины строки и сдвига. Таким образом, если вы считали большой файл в строку, потом взяли из него подстроку и удалили ссылку на первый объект, то память у вас нисколько не освободиться. Так же если вы сериализуете полученную подстроку стандартными средствами и передаете её по сети, то опять же ничего хорошего не выйдет, так как будет сериализован весь оригинальный массив и количество передаваемых байтов по сети вас неприятно удивит. Если это ваш случай, то после нахождения подстроки вы можете воспользоваться конструктором String(String original), который скопирует только реально используемую часть массива. Кстати, если этот конструктор вызывать на стоке длиной равной длине массива символов, то копирования в этом случае происходить не будет, а будет использоваться ссылка на оригинальный массив.
Методы String(byte[] bytes) и getBytes() кодируют и декодируют строку в байты и наоборот, используя кодировку по умолчанию, которую можно переопределить запустив JVM с параметром “Dfile.encoding=”. Если вы кодировку не переопределяете, то используется UTF-8. Если её не присутствует в системе, то – ISO-8859-1. Если нет и её, то JVM завершается с кодом -1. Но когда я пытался выставить “Dfile.encoding=” в java 1.4, то она у меня не подцеплялась. Погуглив немного, я выяснил, что с данной проблемой сталкивался не только я, правда, почему этот параметр не хочет работать, я так и не нашел.
Важно не путать вышеупомянутые методы с String(byte[] ascii, int hibyte), String(byte[] ascii, int hibyte, int offset, int count) и getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin), которые могут некорректно преобразовать символы в байты и обратно.
Переопределение операции “+”
Здесь важно понимать, что данный оператор переопределяется не на уровне байткода, а еще на этапе компиляции java файла в байткод. Если вы декомпилируете байткод, то уже не увидите никакого сложения,а вместо операции
вы уже увидите что-то похожее на
Т.е. смысла засорять ваш код и вместо простого знака “+” городить громоздкий и плохо читаемый код из StringBuilder смысла никакого нет. Даже скажу больше, те кто в java 1.4. вместо плюсов писали StringBuffer, то их код в пятой джаве теперь может работать даже медленнее, так как StringBuffer работает не быстрее чем StringBuilder.
Еще в последних версиях JVM появился замечательный оптимизационный флаг -XX:+OptimizeStringConcat , который при вызове метода StringBuilder .toString() не будет копировать внутренний массив StringBuilder в новый String, а будет использовать тот же массив, таким образом операция конкатенации будет проходить вообще без копирования массивов.
Однако стоить заметить, что в циклах JVM вам может и не заменить операцию “+” на StringBuilder. Вернее она может заменить её на создание нового StringBuilder на каждом шаге цикла, т.е. код
может быть скомпилирован в
Да, раз уж начал об этом писать, то, наверное, стоить упомянуть, что код String a = “b” + “c” скомпилируется в String a = “bc”
Сколько же String занимает места в памяти?
Казалось, бы чего тут сложного – строка это объект с тремя полями int и массивом char’ов, т.е.
[8 bytes (заголовок объекта) + 3*4(int) + 4 (ссылка на массив)] <выравнивание по 8 байтам>+ [8 bytes (заголовок объекта массива) + 4(int длины массива) + 2(char)*длина_строки] <выравнивание по 8 байтам>= 24 + [12 + 2*length] <выравнивание по 8 байтам>= [36 + 2*length]
Получается пустая строка в сумме будет весить 40 байтов.
Теперь посмотрим во что это обернется на 64 битной jvm:
Таким образом пустая строка уже вести 64 байта.
Если же у нас включена опция (-XX:+UseCompressedOops), которая в последних JVM уже работает по умолчанию для куч размером меньше 32 гигов, то имеем следующую арифметику:
[16 bytes (заголовок объекта) + 3*4(int) + 4 (ссылка на массив)] <выравнивание по 8 байтам>+ [16 bytes (заголовок массива) + 2(char)*длина_строки] <выравнивание по 8 байтам>= 32 + [16 + 2*length] <выравнивание по 8 байтам>= 48 + 2*length
Значит для пустой строки в этом случае нам надо 48 байтов.
Так же не стоит забывать о существовании параметра -XX:+UseCompressedStrings, при использовании которого, ANSII строки будут содержать внутри массив байтов, а не символов. Таким образом в формулах выше нужно длину строки умножать на один, а не на два. Т.е. данная опция может сократить размер строк до двух раз.
Пул строк
Как вы уже знаете в java есть пул строк. Туда неявно попадают все литералы (строки в коде оформленные через двойные кавычки “literal”). Так же есть возможность положить строку в пул явно с помощью метода intern(). Например, классы java reflection интенсивно используют этот метод, и все имена полей и методов класса попадают в пул. Соответственно, то же самое происходит при использовании стандартной java сериализации, которая неявно использует reflection. Так же обычно библиотеки работающие с XML кладут в пул названия элементов и атрибутов документов.
Пул располагается в PermGen и хранит строки с помощью слабых ссылок. Таким образом при вызове Full GC, если ваша куча больше не ссылается на строки, находящиеся в пуле, то сборщик мусора их очищает. Однако не стоит увлекаться складыванием всего попало в пул: чем больше в вашем пуле находится строк, тем дольше будет операция на проверку находится ли строка уже там или нет. Мои замеры показали, что зависимость примерно прямо пропорциональна (О(n)) и при размере пула 1 миллион строк, каждая такая операция у меня уже занимала несколько микросекунд.
Говоря о пуле строк, не могу не упомянуть почему некоторые библиотеки при объявлении констант используют вышеописанный метод intern:
Данная конструкция может показаться абсурдной не искушенному программисту. И правда, зачем класть эту строку в пул, если она и так константа и присутствует в единственном экземпляре. И вообще, это же литерал, он и так должен неявно попадать в пул. Однако цель, с которой это делается в библиотеках – совсем не пулинг. Дело в том, что если бы эта константа была объявлена в библиотеке просто как
и вы бы использовали её в своем коде, то при компилировании в байткод эта строка заинлайнилась. И если бы вы потом подложили jar с новой версией библиотеки, и не перекомпилировали ваши исходники, то ваш код изменения в данной константе просто бы не заметил.
Собеседование по Java — работа со строками (String in Java) (вопросы и ответы)
Список вопросов и ответов для собседования по Java по теме «Работа со строками».
К списку вопросов по всем темам
Вопросы
1. Какие “строковые” классы вы знаете?
2. Какие основные свойства “строковых” классов (их особенности)?
3. Можно ли наследовать строковый тип, почему?
4. Дайте определение понятию конкатенация строк.
5. Как преобразовать строку в число?
6. Как сравнить значение двух строк?
7. Как перевернуть строку?
8. Как работает сравнение двух строк?
9. Как обрезать пробелы в конце строки?
10. Как заменить символ в строке?
11. Как получить часть строки?
12. Дайте определение понятию “пул строк”.
13. Какой метод позволяет выделить подстроку в строке?
14. Как разбить строку на подстроки по заданному разделителю?
15. Какой метод вызывается для преобразования переменной в строку?
16. Как узнать значение конкретного символа строки, зная его порядковый номер в строке?
17. Как найти необходимый символ в строке?
18. Можно ли синхронизировать доступ к строке?
19. Что делает метод intern()?
20. Чем отличаются и что общего у классов String, StringBuffer и StringBuilder?
21. Как правильно сравнить значения строк двух различных объектов типа String и StringBuffer?
22. Почему строка неизменная и финализированная в Java?
23. Почему массив символов предпочтительнее строки для хранения пароля?
24. Почему строка является популярным ключом в HashMap в Java?
25. Напишите метод удаления данного символа из строки.
Ответы
1. Какие “строковые” классы вы знаете?
- public final class String implements java.io.Serializable, Comparable , CharSequence
- public final class StringBuffer extends AbstractStringBuilder implements java.io.Serializable, CharSequence
- public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence
2. Какие основные свойства “строковых” классов (их особенности)?
Все строковые классы — final (следовательно от них нельзя унаследоваться).
String.
Строка — объект, что представляет последовательность символов. Для создания и манипулирования строками Java платформа предоставляет общедоступный финальный (не может иметь подклассов) класс java.lang.String. Данный класс является неизменяемым (immutable) — созданный объект класса String не может быть изменен.
StringBuffer
Строки являются неизменными, поэтому частая их модификация приводит к созданию новых объектов, что в свою очередь расходует драгоценную память. Для решения этой проблемы был создан класс java.lang.StringBuffer, который позволяет более эффективно работать над модификацией строки. Класс является mutable, то есть изменяемым — используйте его, если Вы хотите изменять содержимое строки. StringBuffer может быть использован в многопоточных средах, так как все необходимые методы являются синхронизированными.
StringBuilder
StringBuilder — класс, что представляет изменяемую последовательность символов. Класс был введен в Java 5 и имеет полностью идентичный API с StringBuffer. Единственное отличие — StringBuilder не синхронизирован. Это означает, что его использование в многопоточных средах нежелательно. Следовательно, если вы работаете с многопоточностью, Вам идеально подходитStringBuffer, иначе используйте StringBuilder, который работает намного быстрее в большинстве реализаций.
Обработка строк в Java. Часть I: String, StringBuffer, StringBuilder: http://habrahabr.ru/post/260767/
3. Можно ли наследовать строковый тип, почему?
Классы объявлены final, поэтому наследоваться не получится.
4. Дайте определение понятию конкатенация строк.
Конкатенация — операция объединения строк, что возвращает новую строку, что является результатом объединения второй строки с окончанием первой. Операции конкатенации могут быть выполнены так:
Сравнение производительности конкатенации строк:
Оператор ‘+=’ > 92.243 с;
String.concat() > 1.254 с;
StringBuffer > 1.208 с;
StringBuilder > 1.121 с.
Конкатенация и настройки JVM: http://microfork.com/string-concatenation-java/
5. Как преобразовать строку в число?
У каждой обертки для примитивов есть свой метод valueOf(String s) , который возвращает преобразованное численное значение из строки. При этом форматы строки и принимаемого типа должны совпадать. Например:
6. Как сравнить значение двух строк?
Оператор == работает с ссылками объекта String . Если две переменные String указывают на один и тот же объект в памяти, сравнение вернет результат true . В противном случае результат будет false , несмотря на то что текст может содержать в точности такие же символы. Оператор == не сравнивает сами данные типа char . Для сравнения посимвольно необходимо использовать метод equals();
7. Как перевернуть строку?
Можно и алгоритмом переставляя каждый char , но это на ваше усмотрение:).
8. Как работает сравнение двух строк?
Строка в Java — это отдельный объект, который может не совпадать с другим объектом, хотя на экране результат выводимой строки может выглядеть одинаково. Просто Java в случае с логическим оператором == (а также != ) сравнивает ссылки на объекты.
Метод equals сравнивает посимвольно на эквивалентность.
9. Как обрезать пробелы в конце строки?
10. Как заменить символ в строке?
Можно использовать метод replace(CharSequence target, CharSequence replacement) , который меняет символы в строке. Можно преобразовать в массив символов и заменить символ там. Можно использовать StringBuilder и метод setCharAt(int index, char ch)
11. Как получить часть строки?
Метод substring(int beginIndex, int lastIndex) — возвращает часть строки по указанным индексам.
12. Дайте определение понятию “пул строк”.
Пул строк – это набор строк, который хранится в памяти Java heap. Мы знаем, что String это специальный класс в Java, и мы можем создавать объекты этого класса, используя оператор new точно так же, как и создавать объекты, предоставляя значение строки в двойных кавычках.
Диаграмма ниже объясняет, как пул строк размещается в памяти Java heap и что происходит, когда мы используем различные способы создания строк.
Пул строк возможен исключительно благодаря неизменяемости строк в Java и реализации идеи интернирования строк. Пул строк также является примером паттерна Приспособленец (Flyweight).
Пул строк помогает экономить большой объем памяти, но с другой стороны создание строки занимает больше времени.
Когда мы используем двойные кавычки для создания строки, сначала ищется строка в пуле с таким же значением, если находится, то просто возвращается ссылка, иначе создается новая строка в пуле, а затем возвращается ссылка.
Тем не менее, когда мы используем оператор new, мы принуждаем класс String создать новый объект строки, а затем мы можем использовать метод intern() для того, чтобы поместить строку в пул, или получить из пула ссылку на другой объект String с таким же значением.
Ниже приведен пример, показывающий работу пула строк.
13. Какой метод позволяет выделить подстроку в строке?
В дополнении к «как получить часть строки» можно использовать метод string.indexOf(char c) , который вернет индекс первого вхождения символа. Таким образом потом можно использовать этот номер для выделения подстроки с помощью substring();
14. Как разбить строку на подстроки по заданному разделителю?
Мы можем использовать метод split(String regex) для разделения строки на массив символов, используя в качестве разделителя регулярное выражение. Метод split(String regex, int numOfStrings) является перегруженным методом для разделения строки на заданное количество строк. Мы можем использовать обратную черту для использования специальных символов регулярных выражений в качестве обычных символов.
15. Какой метод вызывается для преобразования переменной в строку?
16. Как узнать значение конкретного символа строки, зная его порядковый номер в строке?
str.charAt(int i) вернет символ в по индексу.
17. Как найти необходимый символ в строке?
str.indexOf(char ch) или lastIndexOf(char c) — вернет индекс первого и последнего вхождения символа.
18. Можно ли синхронизировать доступ к строке?
String сам по себе потокобезопасный класс. Если мы работаем с изменяемыми строками, то нужно использовать StringBuffer .
19. Что делает метод intern()?
Когда метод intern() вызван, если пул строк уже содержит строку, эквивалентную к нашему объекту, что подтверждается методом equals(Object) , тогда возвращается ссылка на строку из пула. В противном случае объект строки добавляется в пул и ссылка на этот объект возвращается.
Этот метод всегда возвращает строку, которая имеет то же значение, что и текущая строка, но гарантирует что это будет строка из пула уникальных строк.
Ниже приведен пример работы метода intern()
20. Чем отличаются и что общего у классов String, StringBuffer и StringBuilder?
В дополнение к ответу вначале приведу сравнение производительности классов.
Сравнение производительности. Linux
Сравнение производительности. Windows XP:
21. Как правильно сравнить значения строк двух различных объектов типа String и StringBuffer?
Привести их к одному типу и сравнить.
22. Почему строка неизменная и финализированная в Java?
Есть несколько преимуществ в неизменности строк:
- Строковый пул возможен только потому, что строка неизменна в Java, таким образом виртуальная машина сохраняет много места в памяти(heap space), поскольку разные строковые переменные указывают на одну переменную в пуле. Если бы строка не была неизмененяемой, тогда бы интернирование строк не было бы возможным, потому что если какая-либо переменная изменит значение, это отразится также и на остальных переменных, ссылающихся на эту строку.
- Если строка будет изменяемой, тогда это станет серьезной угрозой безопасности приложения. Например, имя пользователя базы данных и пароль передаются строкой для получения соединения с базой данных и в программировании сокетов реквизиты хоста и порта передаются строкой. Так как строка неизменяемая, её значение не может быть изменено, в противном случае любой хакер может изменить значение ссылки и вызвать проблемы в безопасности приложения.
- Так как строка неизменная, она безопасна для многопоточности и один экземпляр строки может быть совместно использован различными нитями. Это позволяет избежать синхронизации для потокобезопасности, строки полностью потокобезопасны.
- Строки используются в Java classloader и неизменность обеспечивает правильность загрузки класса при помощи Classloader. К примеру, задумайтесь об экземпляре класса, когда вы пытаетесь загрузить java.sql.Connection класс, но значение ссылки изменено на myhacked.Connection класс, который может осуществить нежелательные вещи с вашей базой данных.
- Поскольку строка неизменная, её hashcode кэшируется в момент создания и нет необходимости рассчитывать его снова. Это делает строку отличным кандидатом для ключа в Map и его обработка будет быстрее, чем других ключей HashMap. Это причина, почему строка наиболее часто используемый объект в качестве ключа HashMap.
23. Почему массив символов предпочтительнее строки для хранения пароля?
Строка неизменяемая в Java и хранится в пуле строк. С тех пор, как она была создана, она остается в пуле, пока не будет удалена сборщиком мусора, поэтому, когда мы думаем, что закончили работу с паролем, он остается доступным в памяти некоторое время, и нет способа избежать этого. Это риск безопасности, поскольку кто-либо, имеющий доступ к дампу памяти сможет найти пароль в виде чистого текста.
Если мы используем массив символов для хранения пароля, мы можем очистить его после того, как закончим с ним работать. Таким образом, мы можем контролировать, как долго он находится в памяти, что позволяет избежать риска безопасности, свойственного строке.
24. Почему строка является популярным ключом в HashMap в Java?
Поскольку строки неизменны, их хэшкод кэшируется в момент создания, и не требует повторного пересчета. Это делает строки отличным кандидатом для ключа в Map и они обрабатываются быстрее, чем другие объекты-ключи HashMap. Вот почему строки преимущественно используются в качестве ключей HashMap.
25. Напишите метод удаления данного символа из строки.
Мы можем использовать метод replaceAll для замены всех вхождений в строку другой строкой. Обратите внимание на то, что метод получает в качестве аргумента строку, поэтому мы используем класс Character для создания строки из символа, и используем её для замены всех символов на пустую строку.
Почему класс String объявлен final в Java?
С тех пор, как я узнал, что класс java.lang.String объявлен как Final в Java, мне было интересно, почему это? Тогда я не нашел ответа, но это сообщение: Как создать реплику класса String в Java? это напомнило мне о моем вопросе.
конечно, String предоставляет все функции, которые мне когда-либо нужны, и никогда не думал о какой-либо операции, которая потребует расширения класса String, но все равно вы никогда не узнаете, что кому-то может понадобиться!
Так, кто-нибудь знает, что было ли намерение дизайнеров, когда они решили сделать его окончательным?
16 ответов:
очень полезно иметь строки, реализованные как неизменяемые объекты. Вы должны прочитать о неизменяемости чтобы понять больше об этом.
одно преимущество неизменяемые объекты это
вы можете поделиться дубликатами, указав их на один экземпляр.
Если строка не была окончательной, вы можете создать подкласс и иметь две строки, которые выглядят одинаково, когда “рассматриваются как струны”, но это на самом деле разные.
это хорошая статья это излагает две причины, уже упомянутые в приведенных выше ответах:
- безопасность: система может раздавать чувствительные биты только для чтения информация не беспокоясь, что они будут изменены
- производительность: неизменяемые данные очень полезно в создании вещей потокобезопасных.
и это, вероятно, самый подробный комментарий в этой статье. Его должен сделайте со строковым пулом в Java и вопросы безопасности. Его о том, как решить, что входит в пул строк. Предполагая, что обе строки равны, если их последовательность символов одинакова, то у нас есть условие гонки на том, кто доберется туда первым и вместе с ним проблемы безопасности. Если нет, то пул строк будет содержать избыточные строки, тем самым теряя преимущество его наличия в первую очередь. Просто прочти это для себя, ладно?
расширение строки будет играть хаос с равными и стажером. JavaDoc говорит равно:
сравнивает эту строку с указанным объектом. Результат является истинным тогда и только тогда, когда аргумент не является нулевым и является строковым объектом, представляющим ту же последовательность символов, что и этот объект.
предполагая, что java.lang.String не был окончательным, а SafeString может равняться a String , и наоборот; потому что они представляют одну и ту же последовательность символов.
что произойдет, если вы подадите заявку intern до SafeString — будет ли SafeString перейти в пул строк JVM? Элемент ClassLoader и все объекты SafeString удерживаемые ссылки будут затем заблокированы на месте в течение всего срока службы JVM. Вы получите условие гонки о том, кто может быть первым, чтобы интернировать последовательность символов-может быть, ваш SafeString выиграет, может быть, a String , или SafeString загружается другим загрузчиком классов (таким образом, другой класс).
если бы вы выиграли гонку в бассейн, это был бы настоящий Синглтон, и люди могли бы доступ ко всей среде (песочнице) через отражение и secretKey.intern().getClass().getClassLoader() .
или JVM может заблокировать это отверстие, убедившись, что в пул были добавлены только конкретные объекты String (и никаких подклассов).
если equals был реализован таким образом, что SafeString != String затем SafeString.intern != String.intern и SafeString должен быть добавлен в пул. Тогда бассейн станет бассейном вместо и все, что вам нужно, чтобы войти в бассейн будет свежий загрузчик классов.
абсолютно самая важная причина, по которой строка является неизменной или окончательной, заключается в том, что она используется механизмом загрузки класса и, следовательно, имеет глубокие и фундаментальные аспекты безопасности.
Если бы строка была изменяемой или не окончательной, запрос на загрузку “java.io.Writer” мог бы быть изменен на загрузку “mil.вогун.DiskErasingWriter”
String является очень основным классом в Java, многие вещи полагаются на то, что он работает определенным образом, например, будучи неизменным.
создание класса final предотвращает подклассы, которые могут нарушить эти предположения.
обратите внимание, что даже сейчас, если вы используете отражение, вы можете разбить строки (измените их значение или хэш-код). Отражение можно остановить с помощью диспетчера безопасности. Если String не было final , каждый мог это сделать.
другие классы, которые не объявляются final позволяют определить несколько сломанных подклассов (вы могли бы иметь List это добавляет к неправильной позиции, например), но, по крайней мере, JVM не зависит от них для своих основных операций.
Как сказал Бруно, речь идет о неизменности. Речь идет не только о строках, но и о любых обертках, например Double, Integer, Character и т. д. Для этого есть много причин:
- потокобезопасность
- безопасность
- куча, которая управляется самой Java (в отличие от обычной кучи, которая является мусором, собранным по-разному)
- управление памятью
в основном это так вы, как программист, можете быть уверены, что ваш строка никогда не будет изменена. Это также, если вы знаете, как это работает, может улучшить управление памятью. Попробуйте создать две одинаковые строки одну за другой, например “hello”. Вы заметите, если вы отлаживаете, что у них одинаковые идентификаторы, это означает, что они точно такие же объекты. Это связано с тем, что Java позволяет вам делать это. Это было бы невозможно, если бы строки были изменчивы. Они могут иметь то же самое, что и я, и т. д. потому что они никогда не изменятся. Так что если вы когда-нибудь решите создать Строки 1,000,000 “привет”, что ты на самом деле сделать, это создать 1,000,000 указатели на “привет”. Кроме того, любая функция в строке или любые обертки по этой причине приведут к созданию другого объекта (снова посмотрите на идентификатор объекта – он изменится).
Aditionally final в Java не обязательно означает, что объект не может измениться (он отличается, например, от C++). Это означает, что адрес, на который он указывает, не может изменить, но можно изменить его свойства и/или атрибуты. Поэтому понимание разницы между неизменностью и окончательностью в некоторых случаях может быть действительно важным.
возможно, это было для упрощения реализации. Если вы создаете класс, который будет наследоваться пользователями класса, то у вас есть целый новый набор вариантов использования для рассмотрения в вашем дизайне. Что произойдет, если они сделают то или иное с помощью поля X proptected? Делая его окончательным, они могут сосредоточиться на правильной работе публичного интерфейса и убедиться, что он надежен.
С большим количеством хороших моментов уже mentined я хотел бы добавить еще один -одна из причин, почему строка неизменна в Java, чтобы позволить строка для кэширования своего хэш-кода, будучи неизменяемой строкой в Java кэширует свой хэш-код, и не вычисляйте каждый раз, когда мы вызываем метод hashcode строки, что делает его очень быстрым, как ключ hashmap, который будет использоваться в hashmap в Java.
короче говоря, поскольку строка неизменна, никто не может изменить ее содержимое один раз создан, который гарантирует, что хэш-код строки будет одинаковым при многократном вызове.
если вы видите String класс был объявлен как
и hashcode() функция выглядит следующим образом –
если это уже компьютер просто вернуть значение.
В дополнение к причинам, указанным в других ответах (безопасность, неизменность, производительность) следует отметить, что String имеет специальную языковую поддержку. Вы можете написать String литералы и есть поддержка + оператора. Позволяет программистам подкласс String , будет поощрять хаки, такие как:
Ну, у меня есть несколько другая мысль, я не уверен, что я прав или нет, но в Java String является единственным объектом, который можно рассматривать как примитивный тип данных, а я имею в виду, что мы можем создать объект String как String name= “java”. Теперь, как и другие примитивные типы данных, которые являются копировать по значению не скопировать ссылки строка, как ожидается, будет иметь такое же поведение, поэтому строка является окончательной. Вот что я подумал об этом. Пожалуйста, игнорируйте, если его полностью нелогичный.
окончательность строк также защищает их как стандарт. В C++ вы можете создавать подклассы string, поэтому каждый магазин программирования может иметь свою собственную версию string. Это привело бы к отсутствию сильного стандарта.
чтобы убедиться, что мы не получим лучшего применения. Конечно, это должен был быть интерфейс.
[edit] Ах, получая более невежественные голоса вниз. Ответ совершенно серьезен. Мне пришлось программировать свой путь вокруг глупой реализации строки несколько раз, что привело к серьезной потере производительности и производительности
помимо очевидных причин, предложенных в других ответах, одна мысль о создании класса String final также может быть связана с накладными расходами на производительность виртуальных методов. Помните, что String-это тяжелый класс, что делает его окончательным, означает отсутствие под-реализации наверняка, означает отсутствие косвенного вызова накладных расходов когда-либо. Конечно, теперь у нас есть такие вещи, как virtual invoke и другие, которые всегда делают такую оптимизацию для вас.
знает ли JVM, что является неизменным? Ответ-Нет, постоянный пул содержит все неизменяемые поля, но все неизменяемые поля / объекты не хранятся только в постоянном пуле. Только мы реализуем его таким образом, чтобы он достигал неизменности и своих особенностей. CustomString может быть реализован без окончательного использования MarkerInterface, который обеспечит специальное поведение java для его объединения, эта функция все еще ожидается!
большинство ответов связаны с неизменяемостью — почему объект строкового типа не может быть обновлен на месте. Здесь много хороших дискуссий, и сообществу Java было бы неплохо принять неизменность в качестве основного. (Не задерживая дыхание.)
однако вопрос OP заключается в том, почему он является окончательным-почему он не может быть продлен. Некоторые здесь взяли это на себя, но я бы согласился с ОП, что здесь есть реальный пробел. Другой язык позволяет разработчикам создавать новые номинальные типы для типа. Например, в Haskell я могу создать следующие новые типы, которые идентичны во время выполнения как текст, но обеспечивают безопасность привязки во время компиляции.
поэтому я бы предложил следующее предложение в качестве улучшения языка Java:
(или, возможно, мы могли бы начать отучать себя от Java)
Допустим, у вас есть Employee класс, который имеет метод greet . Когда greet метод называется он просто печатает Hello everyone! . Так вот что такое ожидаемое поведение на greet метод
теперь, GrumpyEmployee подкласс Employee и заменить greet метод, как показано ниже.
теперь в приведенном ниже коде взгляните на sayHello метод. Это займет Employee экземпляр в качестве параметра и вызывает метод greet надеясь, что он скажет Hello everyone! но то, что мы получаем Get lost! . Это изменение в поведении происходит из-за Employee grumpyEmployee = new GrumpyEmployee();
такая ситуация может быть избежать если Employee класс составила final . Теперь это до вашего воображения количество хаоса нахальный программист может вызвать, если String класс не был объявлен как final .
Если вы создадите строку, как только она рассмотрит это,это объект, если вы хотите изменить это,это невозможно, он создаст новый объект.
Java 8 строки
В Java строки являются объектами класса java.lang.String, который является обычным классом но со специальной поддержкой компилятором. Этот класс представляет строку в виде набора символов UTF-16, где дополнительные символы представляются в виде суррогатных пар, другими словами один символ Юникода может состоять из двух char code unit. Индекс в строке адресует char code unit, а на символ. Дополнительные символы используют две позиции в строке.
char code unit — 16-битный символ char .
char code point — символ юникода. Может состоять из двух char -ов.
Такая путаница из char code unit и char code point произошла из-за того, что первоначально стандарт Юникода 1991 года предполагал 16-битные символы фиксированной длины, однако в последствии он был расширен в 1996 году для возможности представления символов, которым необходимо больше 16 бит.
Все строковые литералы в Java являются объектами класса String .
Примеры создания строк:
Для строковых литералов используются двойный кавычки, а для примитивного типа char — одинарные.
У класса String довольно много конструкторов. Можно, например, создать строку на основе массива байт:
В этих примерах “ISO-8859-1” — это имя кодировки.
Каждая реализация Java поддерживает следующие виды кодировок:
Дополнительно могут поддерживаться другие кодировки, например для Windows обязательно будет поддержка кодировки windows-1251.
Когда компилятор видит строковый литерал, например “Hello world!” , в коде, он создаёт объект класса java . lang . String .
Так как строковые литеры являются объектами, то у них можно вызывать методы:
В Java нет перегрузки операций, но класс строки имеет особую поддержку со стороны компилятора — строк можно соединять с помощью операции «+» (при этом используется java.lang.StringBuilder или java . lang . StringBuffer ):
Конкатенации строковых литералов вида ” goes” + ” to school.” компилятор вычисляет на этапе компиляции, поэтому во время выполнения не будет происходить лишних операций. Можно с помощью «+» разделять строковые константы на несколько строк, компилятор уберёт лишние операции сам и превратит всё в одну строковую литералу:
Каждый объект в Java может участвовать в операции конкатенации (соединения) строк, в этом случае используется метод toString ( ) , (если ссылка на объект равна null, то используется строка “null” ):
Выведет в консоль:
Класс String НЕ имеет специальной поддержки для == , поэтому сравнивать строки нужно либо через метод equals ( ) , либо equalsIgnoreCase ( ) , либо compareTo ( ) , либо compareToIgnoreCase ( ) .
В Java используется пул строковых литералов. Одинаковые строковые литералы всегда ссылаются на один и тот же экземпляр класса String :
Этот код выведет:
- Одинаковые строковые литералы всегда ссылаются на один и тот же экземпляр класса String .
- Экземпляры классы String , вычисленные во время выполнения, создаются заново, автоматически в пуле не участвуют и потому различны.
- Строковые литералы в константных выражениях вычисляются на этапе компиляции и затем расцениваются как обычные литералы.
- С помощью метода intern() можно добавить строку в пул либо получить ссылку на такую строку из пула.
Многие методы строки принимают в качестве параметра или возвращают в качестве результата индекс. Индексация начинается с нуля. Первый char (имеется в виду char code unit, а не символ, разумеется) в строке имеет индекс 0, а последний имеет индекс length() — 1. Если переданный в параметр индекс выходит за пределы строки, то методы генерируют исключение java . lang . IndexOutOfBoundsException .
Некоторые методы принимают в качестве параметров начальный индекс и конечный индекс. В этом случае начальный индекс включается в отрезок, а конечный индекс исключается, так что длина отрезка получается равной конечный индекс минус начальный индекс, как на картинке.
Подобная индексация является стандартом в Java. Всегда когда указывается отрезок в массиве, строке, списке или ещё-где-то, начальный индекс включается в отрезок, а конечный исключается.