Вступление
Путь программиста – сложный и долгий процесс. И в большинстве случаев начинается он с программы, которая выводит Hello World на экран. Java не исключение (см. Lesson: The "Hello World!" Application). Как мы видим, вывод сообщения осуществляется при помощиSystem.out.println("Hello World!");
Если посмотреть в Java API, то метод System.out.println принимает входным параметром String. Про этот тип данных и пойдёт речь.
String как последовательность символов
Собственно, String в переводе с английского – строка. Так и есть, тип String представляет текстовую строку. А чем же является текстовая строка? Текстовая строка - это какая-то упорядоченная последовательность символов, которые идут друг за другом. Символ – char. Последовательность – sequence. Так что да, абсолютно правильно, String является реализациейjava.lang.CharSequence
. А если заглянуть внутрь самого класса String, то внутри него ничто иное как массив char’ов: private final char value[];
У java.lang.CharSequence
довольно простой контракт:
У нас есть метод получения количества элементов, получения конкретного элемента и получения набора элементов + непосредственно сам метод toString, который вернёт this)
Интереснее разобраться в методах, которые пришли к нам в Java 8, а это:
chars()
и codePoints()
Вспоминаем по Tutorial от Oracle «Primitive Data Types», что char - это single 16-bit Unicode character
.То есть по сути char это просто тип размером в половину типа int (32 бита), который представляет числа от 0 до 65535 (см. decimal значения в ASCII Table). То есть при желании мы можем char представить в виде int. И в Java 8 этим воспользовались.
Начиная с 8 версии Java у нас появляется IntStream - стрим для работы с примитивными int'ами. Поэтому в charSequence есть возможность получить IntStream, представляющий или char’ы или codePoint’ы.
Прежде чем перейдём к ним, увидим пример, чтобы показать всё удобство этого подхода. Воспользуемся Tutorialspoint online java compiler’ом и выполним код:
public static void main(String []args){
String line = "aaabccdddc";
System.out.println( line.chars().distinct().count() );
}
Теперь таким незамысловатым способом можно получить кол-во уникальных символов.
CodePoints
Итак, про chars мы увидели. Теперь непонятно, что за code points такие. Понятие codePoint появилось потому, что когда Java появилась, то хватало 16 бит (половина int) чтобы закодировть символ. Поэтому char в java представлен в UTF-16 формате ("Unicode 88" specification). Позже повяился Unicode 2.0, концепция которого заключалась в представлении символа в виде сурогатной пары (2 чаров). Это позволило расширить диапазон возможных значений до значения int. Подробнее см. на stackoverflow: "Comparing a char to a code-point?". Про UTF-16 так же указано и в JavaDoc к Character . Там же, в JavaDoc, сказано, что:In this representation, supplementary characters are represented as a pair of char values, the first from the high-surrogates range, (\uD800-\uDBFF), the second from the low-surrogates range (\uDC00-\uDFFF).
На стандартных алфавитах довольно трудно (а может даже нельзя) воспроизвести это.
Но символы буквами и цифрами не заканчиваются. В японии придумали такую сложную для кодировок штуку, как emoji - язык идеограмм и смайликов. Есть про это интересная статья на википедии: «Эмодзи».
Найдём пример emoji, например такой: «Emoji Ghost». Как мы видим, там даже указан тот самый codePoint (значение = U+1F47B). Указан он в шестнадцатеричном формате. Если перевести в десятичное число, то получим 128123. Это больше, чем позволяет 16 бит (т.е. больше чем 65535). Скопируем его:
К сожалению, платформа JavaRush не поддерживает такие символы в тексте. Поэтому, в пример ниже нужно будет в String вставить значение.
Поэтому, теперь нам будет понятен простой тест:
public static void main(String []args){
String emojiString = "Вставте сюда эмоджи через ctrl+v";
//На один emojiString приходится 2 чара (т.к. не влезает в 16 бит)
System.out.println(emojiString.codePoints().count()); //1
System.out.println(emojiString.chars().count()); //2
}
Как видно, в данном случае 1 codePoint идёт за 2 char’а. Вот такая вот магия.
Character
Как мы увидели выше, String’и в Java состоят из char. Примитивный тип позволяет хранить значение, а вот обёрткаjava.lang.Character
над примитивным типом позволяет сделать много полезного с этим символом. Например, мы можем перевести строку в верхний регистр:
public static void main(String[] args) {
String line = "организация объединённых наций";
char[] chars = line.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (i == 0 || chars[i - 1] == ' ') {
chars[i] = Character.toUpperCase(chars[i]);
}
}
System.out.println(new String(chars));
}
Ну и разные интересности: isAlphabetic()
, isLetter()
, isSpaceChar()
, isDigit()
, isUpperCase()
, isMirrored()
(например, скобки. '(' имеет зеркальное отражение ')').
String Pool
Строки в Java неизменяемы, то есть константны. В том числе об этом указано в самом JavaDoc класса java.lang.String. Второе и тоже очень важное – строки могут задаваться литералами:
String literalString = "Hello, World!";
String literalString = "Hello, World!";
То есть любая строка в кавычках, как указано выше, на самом деле объект. И тут напрашивается вопрос – если мы так часто используем строки и они часто могут быть одинаковыми (например, текст «Ошибка» или «Выполнено успешно»), нельзя ли как-нибудь сделать так, чтобы строки не создавались каждый раз? Кстати, у нас ведь ещё есть Map’ы, где ключом может быть строка. Тогда нам точно нельзя, чтобы одинаковые строки были разными объектами, иначе мы потом не сможем из Map достать объект.
Разработчики Java подумали подумали и придумали String Pool. Это такое место, где хранятся строки, можно назвать это кэшем строк. Попадают туда «сами» не все строки, а только строки, указанные в коде литералом. В пул можно внести строку и самому, но об этом чуть позже.
Итак, в памяти у нас есть где-то этот кэш. Справедливый вопрос: а где этот пул находится? Ответ на него можно найти на stackoverflow: «Where does Java's String constant pool live, the heap or the stack?».
Расположен он в Heap памяти, в особой runtime constant pool области. Runtime constant pool выделяется при создании класса или интерфейса виртуальной машиной из method area - особой области в Heap, доступ к которой есть у всех потоков внутри виртуальной машины Java.
Что же даёт нам String pool? У этого есть несколько преимуществ:
- Не будут создаваться однотипные объекты
- Сравнение по ссылке быстрее, чем посимвольное сравнение через equals
public static void main(String[] args) {
String test = "literal";
String test2 = new String("literal");
System.out.println(test == test2);
}
Как видите, строки одинаковые, но результат будет false. А всё потому, что == сравнивает не по значению, а по ссылке. А вот так работает:
public static void main(String[] args) {
String test = "literal";
String test2 = new String("literal").intern();
System.out.println(test == test2);
}
Только заметим, что new String всё равно мы сделаем. То есть intern нам вернёт String из кэша, а вот изначальный String, по которому мы искали в кэше, будет выброшен на очистку, т.к. никто больше про него не знает. На лицо лишнее потребление ресурсов =( Поэтому, сравнивать строки нужно всегда через equals, чтобы уйти по возможности от внезапных и трудно определяемых ошибок.
public static void main(String[] args) {
String test = "literal";
String test2 = new String("literal").intern();
System.out.println(test.equals(test2));
}
Equals выполняет посимвольное сравнивание строк.
Конкатенация
Как мы помним, строки можно складывать. И как мы помним строки у нас неизменяемы. Так как же тогда это работает? Всё верно, создаётся новая строка, которая состоит из символов складываемых объектов. Существует миллион версий о том, как работает конкатенация через плюс. Кто-то считает что будет каждый раз новый объект, кто-то считает что будет ещё что-то. Но прав может быть кто-то один. И этот кто-то – компилятор javac. Воспользуемся сервисом онлайн компилятора и выполним:
public class HelloWorld {
public static void main(String[] args) {
String helloMessage = "Hello, ";
String target = "World";
System.out.println(helloMessage + target);
}
}
Теперь сохраним это как zip архив, извлечём в каталог и выполним: javap –c HelloWorld
И тут мы всё узнаем:
В цикле, конечно, лучше делать конкатенацию через StringBuilder самим. И не потому что какая-то магия, а чтобы StringBuilder создавался до цикла, а в самом цикле происходил только append. Кстати, тут есть ещё одна интересность.
Есть отличная статья: «Обработка строк в Java. Часть I: String, StringBuffer, StringBuilder». Много полезного в комментариях. Например, указано, что при конкатенации вида
new StringBuilder().append()...toString()
действует intrinsic оптимизация, регулируемая опцией -XX:+OptimizeStringConcat, которая по умолчанию включена.
intrinsic - переводится как "внутренний". Такие вещи JVM обрабатывает особенным образом, обрабатывая их как Native, только без дополнительных затрат на JNI. Подробнее: "Intrinsic Methods in HotSpot VM".
StringBuilder и StringBuffer
Как мы выше видели, StringBuilder очень полезный инструмент. Строки являются immutable, т.е. неизменяемыми. А складывать хочется. Поэтому, нам в помощь даны 2 класса: StringBuilder и StringBuffer. Основное отличие между ними в том, что StringBuffer появился в JDK1.0, в то время как StringBuilder пришёл в java 1.5 как не синхронизированная версия StringBuffer, чтобы снять повышенные затраты на ненужную синхронизацию методов. Оба эти классы являются реализацией абстрактного класса AbstractStringBuilder - A mutable sequence of characters. Внутри хранится массив чаров, который расширяется по правилу: value.length * 2 + 2. По умолчанию размер (capacity) у StringBuilder'а равен 16.Comparable
Строки являются comparable, т.е. реализуют метод compareTo. Выполняется это при помощи посимвольного сравнения. Интересно, что из двух строк выбирается минимальная длинна и по ней выполняется цикл. Поэтому, compareTo вернёт или разницу между int значениями первых несовпавших символов в пределе наименьшей из длинн строк, либо вернёт разницу между длиннами строк, если в пределах минимальной длинны строки все символы совпадают. Такое сравнение называется «лексикографическим».Работа со строками Java
String имеет множество полезных методов:На работу со строками сущесвует множество задач. Например, на Coding Bat.
Так же есть курс на coursera: "Algorithms on Strings".
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ