Метод split разбивает строку на части по разделителю и возвращает массив строк: "I love Java".split(" ") даст {"I", "love", "Java"}. Разделитель тут задается не символом, а регулярным выражением, и отсюда растут почти все неожиданности: спецсимволы вроде точки или вертикальной черты приходится экранировать, ведущий разделитель добавляет в начало массива пустую строку, а пустые строки в конце по умолчанию отбрасываются. Есть и второй вариант метода, с дополнительным параметром limit, который ограничивает число частей.![Эдвард Руки-ножницы в черном костюме, руки с длинными лезвиями разведены в стороны]()
Обрати внимание на различия между последними двумя строками в таблице выше.
В предпоследней строке разделителем выступает символ запятой, поэтому строка разбилась таким образом, что в некоторых словах есть ведущие пробелы.
В последней же строке в качестве разделителя мы использовали символ запятой и символ пробела. Поэтому в результирующем массиве не оказалось строк с ведущими пробелами.
Это просто маленькая деталь, в которой демонстрируется, как важно внимательно подбирать правильный разделитель.
Если мы посмотрим на реализацию обоих методов в исходниках класса String, увидим, что оба они ведут в один и тот же внутренний метод, а вариант с одним аргументом просто подставляет нулевой limit:
Кратко
- split(String regex) возвращает массив строк, разбив исходную по совпадениям с регулярным выражением. Если совпадений нет, в массиве будет один элемент: сама строка целиком.
- Разделитель это регулярное выражение, а не просто символ. Спецсимволы регулярок (точка, вертикальная черта, звездочка, плюс, скобки и другие) нужно экранировать: в коде это выглядит как "\\|" или Pattern.quote("|").
- Ведущий разделитель дает пустую строку первым элементом массива. Разделитель в конце не дает ничего: хвостовые пустые строки при значении limit по умолчанию отбрасываются.
- Второй вариант метода принимает limit: положительный ограничивает длину массива, ноль отбрасывает пустые строки в конце, отрицательный сохраняет их все.
- split(regex) это то же самое, что split(regex, 0), так и написано в исходниках класса String.
- Пустая строка на входе дает массив из одного элемента, пустой строки, а не пустой массив. Поэтому проверка вида arr.length == 0 после split не сработает никогда.
Определение и сигнатура для Java String.split
Метод split в Java разделяет строку на подстроки, используя разделитель, который определяется с помощью регулярного выражения. Приведем сигнатуру метода и начнем наше погружение:
String[] split(String regex)
Из сигнатуры ясны две вещи:- Метод возвращает массив строк.
- Метод принимает строку regex в качестве параметра.
- Метод возвращает массив строк.
В определении есть такие слова: "Метод split в Java разделяет строку на подстроки". Данные подстроки собираются методом в массив и представляют собой его возвращаемое значение.
- Метод принимает строку regex в качестве параметра.
Опять же, вспомним определение: "разделяет строку на подстроки, используя разделитель, который определяется с помощью регулярного выражения". Принимаемый параметр regex это шаблон регулярного выражения, который применяется к исходной строке и по совпадениям определяет в ней символ (или комбинацию символов) разделителя.

Split на практике
Теперь ближе к делу. Представим, что у нас есть строка со словами. Например, такая:I love Java
Нам нужно разбить строку на слова. Мы видим, что в данной строке слова разделены друг от друга пробелами. Пробел это идеальный кандидат на роль разделителя в данном случае.
Так выглядит код решения данной задачи:
public class Main {
public static void main(String[] args) {
String str = "I love Java";
String[] words = str.split(" ");
for (String word : words) {
System.out.println(word);
}
}
}
Выводом метода main будут следующие строки:
I
love
Java
Посмотрим еще на несколько примеров того, как бы отработал метод split:| Строка | Разделитель | Результат работы метода |
|---|---|---|
| "I love Java" | " " (символ пробела) | {"I", "love", "Java"} |
| "192.168.0.1:8080" | ":" | {"192.168.0.1", "8080"} |
| "Красный, оранжевый, желтый" | "," | {"Красный", " оранжевый", " желтый"} |
| "Красный, оранжевый, желтый" | ", " | {"Красный", "оранжевый", "желтый"} |
Ведущий разделитель
Есть еще один важный нюанс. Если исходная строка начинается с разделителя, первым элементом результирующего массива будет пустая строка. На примере это будет выглядеть так: Исходная строка: " I love Java" Разделитель: " " Результирующий массив: { "", "I", "love", "Java" } Но если исходная строка заканчивается разделителем, а не начинается, результат будет иным: Исходная строка: "I love Java " Разделитель: " " Результирующий массив: { "I", "love", "Java" } Смотрим в коде на вариации работы метода split с символом разделителя в конце и/или начале исходной строки:
public class Main {
public static void main(String[] args) {
print("I love Java".split(" "));
print(" I love Java".split(" "));
print("I love Java ".split(" "));
print(" I love Java ".split(" "));
}
static void print(String[] arr) {
System.out.println(Arrays.toString(arr));
}
}
Вывод метода main будет таким:
[I, love, Java]
[, I, love, Java]
[I, love, Java]
[, I, love, Java]
Еще раз обрати внимание: когда в исходной строке первый символ это символ разделителя, в результате в массиве первым элементом будет пустая строка.
Тут есть одна оговорка, и она прямо прописана в документации метода: пустая строка появляется в начале, только если совпадение в начале имело ненулевую длину. Если же шаблон совпадает с пустотой, ведущей пустой строки не будет. Поэтому "abc".split("") вернет {"a", "b", "c"}, а не {"", "a", "b", "c"}.Перегруженный собрат
В классе String есть еще один метод split с такой сигнатурой:
String[] split(String regex, int limit)
У этого метода есть дополнительный параметр limit: он определяет, какое количество раз шаблон regex будет применяться к исходной строке. Ниже пояснения:limit > 0
Применяется limit-1 раз. При этом длина массива не будет превышать значение limit. Последним элементом массива будет часть строки, следующая за последним найденным разделителем. Пример:
public class Main {
public static void main(String[] args) {
print("I love Java".split(" ", 1));
print("I love Java".split(" ", 2));
/*
Вывод:
[I love Java]
[I, love Java]
*/
}
static void print(String[] arr) {
System.out.println(Arrays.toString(arr));
}
}
limit < 0
Шаблон для поиска разделителя применяется к строке столько раз, сколько это возможно. Длина итогового массива может быть любой. Пример:
public class Main {
public static void main(String[] args) {
// Обратите внимание на пробел в конце строки
print("I love Java ".split(" ", -1));
print("I love Java ".split(" ", -2));
print("I love Java ".split(" ", -12));
/*
Вывод:
[I, love, Java, ]
[I, love, Java, ]
[I, love, Java, ]
Обратите внимание: последний элемент массива это
пустая строка, возникшая из-за пробела
в конце исходной строки.
*/
}
static void print(String[] arr) {
System.out.println(Arrays.toString(arr));
}
}
limit 0
Как и в случае с limit < 0, шаблон для поиска разделителя применяется к строке столько раз, сколько это возможно. Итоговый массив может быть любой длины. Если последние элементы равны пустой строке, в итоговом массиве они будут отброшены. Пример:
public class Main {
public static void main(String[] args) {
// Обратите внимание на пробел в конце строки
print("I love Java ".split(" ", 0));
print("I love Java ".split(" ", -1));
/*
Вывод:
[I, love, Java]
[I, love, Java, ]
Первый вызов отбросил пустую строку в конце, второй ее сохранил
*/
}
static void print(String[] arr) {
System.out.println(Arrays.toString(arr));
}
}
Все три случая удобно держать рядом:
| Значение limit | Сколько раз применяется шаблон | Пустые строки в конце | Пример для строки "I love Java " с пробелом на конце |
|---|---|---|---|
| limit больше нуля | Не больше чем limit-1 раз, длина массива не превысит limit | Остаются, если попали в массив | split(" ", 2) даст {"I", "love Java "} |
| limit равен нулю | Столько раз, сколько получится | Отбрасываются | split(" ", 0) даст {"I", "love", "Java"} |
| limit меньше нуля | Столько раз, сколько получится | Сохраняются все | split(" ", -1) даст {"I", "love", "Java", ""} |
public String[] split(String regex) {
return split(regex, 0, false);
}
public String[] split(String regex, int limit) {
return split(regex, limit, false);
}
Что за третий аргумент false, разберемся прямо сейчас.А с Java 21 методов стало три
Помнишь вопрос из вступления, сколько же методов split в классе String на самом деле? Долгое время ответ был «два», но в Java 21 у них появился третий родственник:
String[] splitWithDelimiters(String regex, int limit)
Он делает ровно то же, что и split, только не выбрасывает разделители, а возвращает их вместе с частями. В массиве каждая часть идет в паре со своим разделителем, и только у последней части разделителя нет:
public class Main {
public static void main(String[] args) {
String userInfo = "135|bender|bender@gmail.com";
System.out.println(Arrays.toString(userInfo.splitWithDelimiters("\\|", 0)));
// Вывод: [135, |, bender, |, bender@gmail.com]
}
}
Параметр limit тут работает по тем же правилам, что и у обычного split. А тот самый третий аргумент во внутреннем методе как раз и означает «разделители не возвращать»: split зовет его с false, а splitWithDelimiters с true.Различные примеры
В рабочей практике иногда бывает так, что у нас есть строка, составленная по определенным правилам. Данная строка может "приходить" в нашу программу откуда угодно:- из стороннего сервиса;
- из запроса к нашему серверу;
- из конфигурационного файла;
- и т.д.
user_id|user_login|user_email
Для примера возьмем конкретные значения:
135|bender|bender@gmail.com
И вот перед программистом стоит задача: написать метод, который отправляет электронное письмо пользователю. В его распоряжении информация о юзере, записанная в указанном выше формате.
Ну а подзадача, которую мы продолжим разбирать, это вычленить email-адрес из общей информации о пользователе.
Это один из примеров, когда метод split может оказаться полезным. Ведь если мы взглянем на шаблон, мы поймем: чтобы вычленить email-адрес пользователя из всей информации, нам нужно всего лишь разделить строку с помощью метода split. Тогда email-адрес будет лежать в последнем элементе результирующего массива.
Приведем пример такого метода, который принимает в себя строку, содержащую информацию о пользователе, и возвращает email пользователя. Для упрощения предположим, что данная строка всегда соответствует нужному нам формату:
public class Main {
public static void main(String[] args) {
String userInfo = "135|bender|bender@gmail.com";
System.out.println(getUserEmail(userInfo));
// Вывод: bender@gmail.com
}
static String getUserEmail(String userInfo) {
String[] data = userInfo.split("\\|");
return data[2]; // или data[data.length - 1]
}
}
Обратите внимание на разделитель: "\\|".
Так как в регулярных выражениях “|” это специальный символ, на котором завязана определенная логика, использовать его как обычный (тот, который мы хотим найти в исходной строке) просто так не выйдет: его нужно экранировать.
И вот тут легко запутаться в двух уровнях экранирования. Самому регулярному выражению хватает одного обратного слеша, \|. Но записываем-то мы это выражение строковым литералом Java, а в литерале обратный слеш и сам служебный, так что его тоже надо удвоить. Отсюда и "\\|" в коде: первый слеш экранирует второй, а до движка регулярных выражений доезжает ровно \|.
Если возиться со слешами не хочется, есть готовый способ: метод Pattern.quote() превращает любую строку в шаблон, который ищется буквально. Вместо split("\\|") можно написать split(Pattern.quote("|")), и никакие спецсимволы внутри уже не помешают.
Рассмотрим другой пример. Допустим, у нас есть информация о заказе, которая записана примерно в таком формате:
item_number_1,item_name_1,item_price_1;item_number_2,item_name_2,item_price_2;...;item_number_n,item_name_n,item_price_n
Ну или же возьмем конкретные значения:
1,огурцы,20.05;2,помидоры,123.45;3,зайцы,0.50
Перед нами стоит задача: рассчитать итоговую стоимость заказа. Здесь нам придется применять метод split несколько раз.
Первым шагом мы разделим строку через символ ";", на составные части. Тогда в каждой такой части у нас будет информация об отдельном товаре, которую мы сможем обработать в дальнейшем.
А затем, в рамках каждого товара будем разделять информацию с помощью символа "," и брать из результирующего массива элемент с определенным индексом (в котором хранится цена), приводить ее к числовому виду и составлять итоговую стоимость заказа.
Напишем метод, который все это посчитает:
public class Main {
public static void main(String[] args) {
String orderInfo = "1,огурцы,20.05;2,помидоры,123.45;3,зайцы,0.50";
System.out.println(getTotalOrderAmount(orderInfo));
// Вывод: 144.0
}
static double getTotalOrderAmount(String orderInfo) {
double totalAmount = 0d;
final String[] items = orderInfo.split(";");
for (String item : items) {
final String[] itemInfo = item.split(",");
totalAmount += Double.parseDouble(itemInfo[2]);
}
return totalAmount;
}
}
Попробуй самостоятельно разобраться с тем, как работает данный метод.
На основе данных примеров можно сказать, что метод split используется тогда, когда у нас есть некоторая информация в строковом виде, из которой нам необходимо вычленить какую-то более специфическую информацию.
Что есть рядом со split
Метод split закрывает не все задачи с разбиением строк, и рядом с ним живет несколько инструментов, о которых полезно знать заранее.Один шаблон на много строк
Внутри split есть быстрый путь: если разделитель это один символ и он не из набора спецсимволов регулярок, либо два символа, где первый обратный слеш, строка делится напрямую, без регулярных выражений. Во всех остальных случаях на каждый вызов создается новый Pattern. Поэтому, если разбирать тысячи строк одним и тем же непростым шаблоном, его лучше скомпилировать один раз:
private static final Pattern SEPARATOR = Pattern.compile("\\s*,\\s*");
// дальше в коде
String[] parts = SEPARATOR.split(line);
Сразу в стрим
У Pattern с Java 8 есть метод splitAsStream(): он отдает не массив, а поток строк, и это удобно, когда части все равно нужно тут же отфильтровать или преобразовать.
List<String> tags = Pattern.compile("\\s*,\\s*")
.splitAsStream("java, spring, sql")
.toList();
// [java, spring, sql]
Обратная операция
Если части нужно склеить назад, писать цикл не надо: с Java 8 есть String.join().
String csv = String.join(",", "java", "spring", "sql");
// java,spring,sql
Второй вариант метода принимает не набор аргументов, а любую коллекцию строк, так что результат split в него отправляется без переделок.
Разбить по строкам
А вот для переводов строк split лучше не звать вовсе. С Java 11 у String есть метод lines(), который сам разбирается со всеми тремя вариантами перевода строки: \n, \r и \r\n.
"первая\nвторая\nтретья".lines().forEach(System.out::println);
Итоги
Мы рассмотрели с тобой метод split класса String. Он нужен для разделения строки на составные части с помощью специального разделителя. Метод возвращает массив строк (составные части строки). Принимает в себя регулярное выражение, по которому находит символ(ы) разделителя. Мы рассмотрели различные тонкости работы данного метода:- ведущий символ разделителя;
- перегруженный собрат с двумя аргументами.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ