Appearance
Подробнее о строках
Ремарка: Эта лекция исключительно опциональна!
Так как строки используются очень часто, я решил отдельно расширить лекцию о них. Поговорим сначала о важных свойствах этих строк.
Неизменяемость строк
Самое важное о строках: СТРОКИ НЕИЗМЕНЯЕМЫ.
Например:
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 используется для дробных чисел
// а мы пытаемся вставить туда целое число.