String.substring: примеры (JAVA)

Извлечение подстрок в Java
Раздел: Строки (String) - базовые операции
String.substring(int beginIndex, int endIndex): String

Описание метода String.substring

Метод String.substring принадлежит классу java.lang.String и возвращает часть исходной строки. Доступны две перегрузки:

  • String substring(int beginIndex) - возвращает подстроку, начинающуюся с позиции beginIndex и продолжающуюся до конца строки.
  • String substring(int beginIndex, int endIndex) - возвращает подстроку, начинающуюся с включительной позиции beginIndex и заканчивающуюся невключительно на позиции endIndex.

Параметры и возвращаемые значения:

  • beginIndex - целое неотрицательное значение, указывающее начальную позицию символа (нумерация с нуля). При вызове на null возникнет NullPointerException, поскольку это метод экземпляра.
  • endIndex - целое значение, обозначающее позицию, до которой берется подстрока (невключительно). Если не указано, берется до конца строки.
  • Возвращаемое значение - новый объект String, содержащий выбранные символы. В современных реализациях JVM при вызове создается новая строка в памяти (копирование символов). Возвращаемая строка может быть пустой, если beginIndex == endIndex.

Исключения:

  • IndexOutOfBoundsException - если beginIndex < 0, endIndex > length() или beginIndex > endIndex.
  • NullPointerException - при вызове на null ссылке.

Особенности поведения:

  • Индексация символов осуществляется по char единицам Java. При наличии суррогатных пар Unicode возможен разрыв символа при простом указании индексов. Для корректной работы с кодовыми точками следует использовать методы с учетом codePoint'ов (например, offsetByCodePoints или работу с int массивом codePoints).
  • Производительность: в исторических реализациях JVM подстроки могли ссылаться на общий массив символов родительской строки, что влияло на потребление памяти. Современные реализации копируют данные, то есть операция обычно выполняется за время, пропорциональное длине подстроки.

Короткие примеры применения

Ниже несколько простых примеров с кодом и результатом.

Пример 1: извлечение до конца строки

String s = "Hello, world";
String sub = s.substring(7);
System.out.println(sub);
world

Пример 2: извлечение по диапазону (begin inclusive, end exclusive)

String s = "Hello, world";
String sub = s.substring(0, 5);
System.out.println(sub);
Hello

Пример 3: пустая подстрока (равные индексы)

String s = "abc";
String sub = s.substring(1, 1);
System.out.println("<" + sub + "> (length=" + sub.length() + ")");
<> (length=0)

Пример 4: выход за границы с обработкой исключения

String s = "abc";
try {
    String sub = s.substring(2, 5);
    System.out.println(sub);
} catch (IndexOutOfBoundsException e) {
    System.out.println(e.getClass().getSimpleName() + ": " + e.getMessage());
}
IndexOutOfBoundsException: begin 2, end 5, length 3

Похожие методы в Java и их особенности

  • CharSequence.subSequence(int start, int end) - возвращает CharSequence, обычно делегирует к String.substring для строк. Полезен при работе с абстрактными реализациями символосодержащих объектов.
  • StringBuilder.substring и StringBuffer.substring - аналогичны по семантике, но применяются к изменяемым буферам; возвращают String.
  • Библиотечные утилиты, например org.apache.commons.lang3.StringUtils.substring, обрабатывают null безопасно и позволяют отрицательные или выходящие за пределы индексы без исключения (корректируются автоматически).
  • Регулярные выражения и Matcher - применяются при сложных случаях извлечения по шаблону, когда индексы заранее неизвестны.

Аналоги в других языках и отличия

  • JavaScript
    let s = "Hello";
    console.log(s.substring(1, 4));
    console.log(s.slice(1, 4));
    ell
    ell
    Особенности: substring принимает begin и end (end невключительно) и меняет местами параметры, если begin > end; slice поддерживает отрицательные индексы.
  • Python
    s = "Hello"
    print(s[1:4])
    print(s[1:])
    ell
    ello
    Особенности: срезы поддерживают отрицательные индексы и работают с кодовыми точками Unicode корректно (Python использует абстракцию по символам).
  • PHP
    echo substr("Hello", 1, 3);
    ell
    Особенности: второй аргумент это длина, индексация с нуля, поддержка отрицательных значений для позиционирования с конца.
  • C#
    string s = "Hello";
    Console.WriteLine(s.Substring(1, 3));
    ell
    Отличие: второй параметр - длина подстроки, а не индекс конца.
  • Lua
    print(string.sub("Hello", 2, 4))
    ell
    Отличие: индексация с единицы и конец включается в диапазон.
  • Go
    s := "Hello"
    fmt.Println(s[1:4])
    ell
    Особенность: срез работает по байтам. При работе с UTF-8 требуется предварительно конвертировать в []rune для корректных границ символов.
  • Kotlin
    val s = "Hello"
    println(s.substring(1, 4))
    ell
    Отличие: синтаксис близок к Java; доступна дополнительная функция slice для диапазонов.
  • SQL (пример MS SQL)
    SELECT SUBSTRING('Hello', 2, 3);
    ell
    Отличие: индексация с единицы, второй аргумент - длина.

Типичные ошибки и их примеры

  • IndexOutOfBoundsException - самая распространенная ошибка при неверных границах.
    String s = "abc";
    String t = s.substring(-1);
    IndexOutOfBoundsException: begin -1, end 3, length 3
  • NullPointerException - вызов на null ссылке.
    String s = null;
    String t = s.substring(0);
    NullPointerException
  • Разбиение суррогатной пары (нарушение Unicode) - при работе с эмодзи или символами за пределами BMP можно получить некорректный символ:
    String s = "A😀B"; // A????B (эмодзи состоит из суррогатной пары)
    System.out.println(s.substring(1, 2));
    �� (полусуррогат, некорректное отображение)
    Рекомендуется использовать методы, учитывающие codePoint'ы, чтобы избежать подобных разрывов.
  • Ожидание нулевой стоимости операции - предположение, что substring всегда O(1). В современных JVM операция часто копирует данные, поэтому для очень длинных строк и частых подстрок возможны значительные расходы памяти и CPU.

Изменения в реализации в разных версиях JVM

  • В ранних версиях JVM (до некоторых релизов Java 7) подстроки могли разделять внутренний массив символов родительской строки, что позволяло создавать O(1) subString, но могло привести к утечкам памяти при долгоживущих коротких подстроках от больших строк.
  • После исправлений в более поздних релизах Java (включая обновления Java 7 и далее) подстрока копирует данные, чтобы избежать удержания лишней памяти. Это изменило характеристики производительности на O(n) по длине подстроки.
  • С введением компактных строк в Java 9 внутреннее представление строк изменилось (используется массив байт и флаг coder). Семантика substring осталась прежней, но внутренние детали копирования и представления символов стали другими.
  • Сигнатура и поведение на уровне Java API неизменны на протяжении больших версий, изменения касались внутренних оптимизаций.

Расширенные примеры использования и нестандартные случаи

1. Корректное извлечение по кодовым точкам (без разрыва суррогатных пар)

Пример java
String s = "A\uD83D\uDE00B"; // A????B
int startCp = 1; // в терминах code points
int lenCp = 1;
int beginIndex = s.offsetByCodePoints(0, startCp);
int endIndex = s.offsetByCodePoints(beginIndex, lenCp);
String sub = s.substring(beginIndex, endIndex);
System.out.println(sub);
????

Пояснение: offsetByCodePoints переводит позицию в кодовые точки в индекс char, предотвращая разрыв суррогатных пар.

2. Извлечение по совпадению регулярного выражения

Пример java
String s = "id:12345; name:John";
java.util.regex.Matcher m = java.util.regex.Pattern.compile("id:(\\d+)").matcher(s);
if (m.find()) {
    String id = s.substring(m.start(1), m.end(1));
    System.out.println(id);
}
12345

Пояснение: индекс можно получить из Matcher и затем взять подстроку по этим индексам.

3. Поиск и извлечение без дополнительной аллокации буфера (через StringBuilder)

Пример java
StringBuilder sb = new StringBuilder("Hello, world");
String s = sb.substring(7, 12); // возвращает String, но исходный буфер остается доступен
System.out.println(s);
world

Пояснение: у StringBuilder есть метод substring, поведение аналогично, но сам буфер остается изменяемым.

4. Итерация по подстрокам в потоке и агрегация

Пример java
String[] parts = {"apple:1", "banana:2", "cherry:10"};
java.util.List names = java.util.Arrays.stream(parts)
    .map(p -> p.substring(0, p.indexOf(':')))
    .toList();
System.out.println(names);
[apple, banana, cherry]

5. Выделение границ слов с помощью BreakIterator для локализации

Пример java
String s = "Это тест: ???? пример";
java.text.BreakIterator bi = java.text.BreakIterator.getWordInstance(java.util.Locale.getDefault());
bi.setText(s);
int start = bi.first();
for (int end = bi.next(); end != BreakIterator.DONE; start = end, end = bi.next()) {
    String word = s.substring(start, end);
    if (Character.isLetterOrDigit(word.codePointAt(0))) {
        System.out.println("word: '" + word + "'");
    }
}
word: 'Это'
word: 'тест'
word: 'пример'

Пояснение: BreakIterator позволяет выделять границы слов или символов корректно для выбранной локали, не разрезая сложные символы.

джава String.substring function comments

En
String.substring Returns a string that is a substring of this string