Skip to content
Содержание

Подробнее о строках

Ремарка: Эта лекция исключительно опциональна!

Так как строки используются очень часто, я решил отдельно расширить лекцию о них. Поговорим сначала о важных свойствах этих строк.

Неизменяемость строк

Самое важное о строках: СТРОКИ НЕИЗМЕНЯЕМЫ.

Например:

java
String a = "qwerty"; // создали строку
a = "ytrewq"; // изменили же строку?..

Не изменили.

Во второй строке a = "ytrewq"; мы просто создали новую строку "ytrewq" и переназначили ссылку переменной a на эту новую строку. Любая операция, которая кажется изменяющей строку (конкатенация, замена символов, перевод регистра и т.д.) на самом деле всегда создаёт новый объект String, а исходный остаётся без изменений.

А зачем так сделано?

Скорее всего ничего из следующего текста вы не поймёте, но всё же:

  • Безопасность: строки широко используются как ключи в map, параметры методов, имена классов и т.д. Если бы строку можно было изменить, это нарушило бы целостность многих структур.
  • Потокобезопасность: неизменяемые объекты можно свободно разделять между потоками.
  • Кэширование: хэш-код строки можно вычислить один раз и хранить.

Пул строк

Строки в Java помещаются в специальную область памяти - пул строк. Помните я упоминал ссылочные типы данных? И рассказывал, что нельзя сравнивать ссылочные типы через ==. Потому что в таких случаях сравнивается не содержимое элемента, а ссылки на объект в памяти. Напомню:

java
int[] a = {1, 2, 3, 4, 5}; // массивы - ссылочный тип данных
int[] b = {1, 2, 3, 4, 5}; // такое же содержимое

IO.println(a == b); // false

Массив хранится в памяти, а переменные a и b хранят ссылки на эти массивы. Поэтому и конструкция a == b сравнивает ссылки, а не элементы массивов.
К чему всё это? Строки - тоже ссылочный тип данных. Попробуем сделать такое же сравнение, что и в примере с массивами:

java
String a = "Hello";
String b = "Hello";

IO.print(a == b); // true

Сравнили вроде ссылки, но почему-то в выводе получили true. Дело в том, что каждая строка, которая создаётся в программе, помещается в пул. При создании строки, которая уже создавалась ранее в программе, мы не создаём вторую такую же строку, а берём ссылку на созданную ранее строку из пула строк. Поэтому, в примере выше, ссылки a и b одинаковые.

Но это не значит, что значения строк можно сравнивать через ==. Для этого мы используем метод equals:

java
String a = "Hello";
String b = "Hello";
String c = "Goodbye";

IO.println(a.equals(b)); // true
IO.println(a.equals(c)); // false

Создание строк с помощью new

String - это класс. Экземпляры класса мы вроде как создаём через new (как, например, при создании сканнера Scanner s = new Scanner).
Строки тоже можно создавать с помощью new. В чём разница?

java
String a = "Hello"; // создаём строку с помощью литерала (без new), она идёт в пул
String b = new String("Hello"); // создаём строку через new. ОНА НЕ ПОПАДАЕТ В ПУЛ

Какой вариант создания использовать? В 99.99% случаев - через литерал.

Основные методы класса String

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

МетодОписание
length()длина строки
charAt(int index)получение символа по индексу
substring(int begin, int end)получение подстроки (end не включительно)
indexOf(String str) / lastIndexOf(...)поиск подстроки
contains(CharSequence s)проверка наличия символа/строки в строке
startsWith(String prefix) / endsWith(...)проверка префикса/суффикса
equals(Object obj)сравнение содержимого
equalsIgnoreCase(String another)то же, что и выше, но без учёта регистра
toUpperCase()БОЛЬШИЕ БУКВЫ (всю строку к верхнему регистру)
toLowerCase()маленькие буквы (всю строку к нижнему регистру)
strip()удаление пробелов по краям строки
replace(char old, char new)замена символов
replaceAll(String regex, String replacement)замена по регулярному выражению
split(String regex)разбиение на массив по регулярному выражению
join(CharSequence delimeter, ...)объединение элементов с разделителем
isBlank()проверка на пустоту
repeat(int count)повторение строки
formatted(Object... args)форматирование строки

И теперь собственно подробнее о каждом. Я также указываю полную сигнатуру (как именно метод объявлен) методов, чтобы вы понимали что он возвращает и какие параметры принимает.

int length()

Просто возвращает длину строки.

java
String a = "Hello";
int size = a.length();
IO.println(size); // 5

char charAt(int index)

Возвращает символ из строки по его индексу. По сути как обращение к элементу массива по индексу. И индексация начинается также с нуля. Принимает индекс, возвращает char.

java
String a = "Hello";
char e = a.charAt(1);
IO.println(e); // 'e'

String substring(...)

Метод извлекает из строки подстроку в указанном диапазоне индекса.

String substring(int beginIndex)

Извлекаем подстроку из строки от индекса beginIndex до конца строки.

java
String a = "Hello World".substring(6); // "World"

String substring(int beginIndex, int endIndex)

Извлекаем подстроку в диапазоне от beginIndex до endIndex. endIndex не включается в диапазон.

java
"Hello World".substring(0, 5);     // "Hello"

int indexOf(String str) / lastIndexOf(String str)

Возвращает индекс первого/последнего вхождения символа или подстроки. Возвращает -1, если не найдено. Также может принимать char вместо String.

java
"Hello World".indexOf('o');        // 4
"Hello World".indexOf("World");    // 6
"Hello World".indexOf("xyz");      // -1

"Hello World".lastIndexOf('o');    // 7
"Hello World".lastIndexOf("l");    // 3

boolean startsWith(String prefix) / endsWith(...)

Проверяет, начинается ли строка с указанной последовательности символов (префикса) / заканчивается ли строка указанной последовательности символов (суффикса).

java
String str = "Hello World";
str.startsWith("Hello"); // true
str.startsWith("World"); // false
str.endsWith("World"); // true
str.endsWith("Hello"); // false

str.startsWith("hello"); // false (ЧУВСТВИТЕЛЕН К РЕГИСТРУ)

boolean equalsIgnoreCase(String another)

Сравнивает значения двух строк без учёта регистра.

java
String a = "Hello";
String b = "hello";
a.equalsIgnoreCase(b); // true
a.equals(b); // false, потому что регистрозависимый

String toUpperCase() / toLowerCase()

Приводит всю строку к верхнему / нижнему регистру.

java
String a = "HeLlO";
a.toUpperCase(); // "HELLO"
a.toLowerCase(); // "hello"

strip()

Убирает пробелы и другие пустые символы в начале и конце строки.

java
String a = "   hello   ";
a.trim(); // "hello"

String replace(old, new)

(Типы данных у параметров не указал, потому что туда можно указать либо char, либо String)
Заменяет символ/подстроку в исходной строке на указанный символ/подстроку.

java
String str = "Java is great. Java is fast.";

// Замена символа
System.out.println(str.replace('a', 'A'));
// "JAvA is greAt. JAvA is fAst."
  
// Замена подстроки
System.out.println(str.replace("Java", "Python"));
// "Python is great. Python is fast."
  
// Если подстрока не найдена, возвращается исходная строка
System.out.println(str.replace("C++", "Go"));
// "Java is great. Java is fast."

String[] split(String regex)

split() разбивает строку на массив подстрок (String[]) вокруг совпадений с заданным регулярным выражением (разделителем).

java
String str = "apple,banana,cherry";


String[] fruits = str.split(",");
  
// Результат: ["apple", "banana", "cherry"]
System.out.println(Arrays.toString(fruits));

Нюанс: разделитель тут - регулярное выражение. Что такое регулярное выражение - объяснять я не буду, сами почитайте. Просто примите к сведению.

static String join(String delimiter, String... elements)

Объединяет элементы коллекции или массива в одну строку, вставляя между ними указанный разделитель. Обратите внимание: этот метод статический. Поэтому вызывается не на строке, а на классе String. Принимает разделитель и либо неограниченное количество строк, либо массив строк.

java
String result = String.join(", ", "Java", "Python", "C++");
System.out.println(result); // Вывод: "Java, Python, C++"

или:

java
String[] colors = {"Red", "Green", "Blue"};
String result = String.join("-", colors);
System.out.println(result); // Вывод: "Red-Green-Blue"

boolean isBlank()

Проверяет, является ли строка пустой или состоит только из пробельных символов (whitespace).

Возвращает:

  • true - если строка имеет длину 0 ИЛИ содержит только пробельные символы (пробелы, табуляции, переносы строк и т.д.).
  • false - если в строке есть хотя бы один видимый символ.
java
String s1 = "";
String s2 = "   ";
String s3 = "\t \n";
String s4 = "Hello";

System.out.println(s1.isBlank()); // true
System.out.println(s2.isBlank()); // true  (только пробелы)
System.out.println(s3.isBlank()); // true  (табы и переносы считаются blank)
System.out.println(s4.isBlank()); // false

String repeat(int count)

Этот метод возвращает новую строку, состоящую из исходной строки, повторенной указанное количество раз.

java
String str = "Na";
System.out.println(str.repeat(3)); // "NaNaNa", MCR-референс

String formatted(Object... args)

Он позволяет подставлять аргументы в строку-шаблон, используя тот же синтаксис, что и printf.

java
String name = "Аркадий";
int age = 42;
String result = "Привет, %s! Тебе %d лет.".formatted(name, age);
// Привет, Андрей! Тебе 42 лет.

Что за %s и %d? Это спецификаторы формата. Вместо них подставляются переданные значения.

  • %s - строка
  • %d - целое число
  • %f - число с плавающей точкой
  • %n - перенос строки (зависит от ОС)
  • %t - дата/время Например:
java
int a = 5;
String result = "Введенное число %f".formatted(a);
// Ошибка. Спецификатор %f используется для дробных чисел
// а мы пытаемся вставить туда целое число.

Я ХОЧУ СПАТЬ, ПОЭТОМУ ЛЕКЦИЮ ДОПИШУ ПОТОМ