Классы для работ со строками

Классы для работ со строками

Дорогу осилит идущий

В рамках этого урока мы разберемся с тем, какие строковое типы существуют в Java. Разберем плюсы и минусы каждого из них, включая известный нам тип String.

Для начала посмотрим на упрощенную иерархию строковых классов:


Интерфейс CharSequence

Все строковые типы в Java реализуют интерфейс CharSequence. Его иногда можно увидеть в качестве параметра методов, принимающих строку в любом виде. Такие методы, в том числе, позволяют совмещать работу с разными реализациями строк, например, конкатенировать их.

Данный интерфейс содержит ряд методов, которые реализует каждый из наследников. Рассмотрим те из них, которые могут быть полезны нам:

  • charAt(). Метод, принимающий индекс (порядковый номер) символа в строке (считая с нулевого) и возвращающий символ, находящийся под этим индексом;
  • isEmpty(). Boolean-метод, возвращающий true, если строка пустая;
  • length(). Метод, возвращающий длину (количество символов) строки.

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


StringBuilder и StringBuffer

Оба эти класса являются наследником абстрактного класса AbstractStringBuilder, именно в нем объявлены все публичные методы этих классов.

Особенностью этих реализаций является то, что они, в отличии от String, изменяемы. Соответственно конкатенация строк с использованием StringBuilder и StringBuffer будет дешевле за счет того, что при каждой конкатенации не нужно создавать новый объект.

Отличительной особенностью StringBuffer является потокобезопасность. Это означает, что он гарантирует корректную работу в многопоточной среде, в том числе ситуации, когда с объектом StringBuffer работают несколько потоков одновременно.

StringBuilder не гарантирует потокобезопасности, но за счет этого работает быстрее.

Методы, которые нас интересуют (методы CharSequence также доступны) в этих классах:

  • append(). Перегружен под разные типы данных. Аналогичен оператору «+» для строк — добавляет переданный параметр в конец строки;
  • insert(). Перегружен под разные типы данных. В большинстве реализаций, первый параметр — смещение (индекс элемента), второй — данные, которые надо вставить в подстроку. Пример:
StringBuilder stringBuilder = new StringBuilder("000"); //"000"
stringBuilder.insert(1, "111"); //"011100"
  • compareTo(). Сравнивает строки посимвольно (условно, в алфавитном порядке, на самом деле - по кодам каждого символа). Если параметр, переданный в метод, меньше строки, для которой метод был вызван — вернет отрицательное число, если больше — положительное. Если строки равны — вернет 0 (ноль). Как правило, этот и подобные ему методы используются для различных сортировок;
  • delete(). Принимает два параметра типа intstart и end. Удаляет из строки элементы с индекса start по индекс end (не включительно);
  • deleteCharAt(). Принимает параметром индекс элемента. Удаляет его из строки;
  • replace(). Если метод delete() удаляет элементы по указанному диапазону индексов, то replace() заменяет подстроку, ограниченную индаксами другой подстрокой — переданной в качестве параметра в метод;
  • reverse(). Разворачивает строку: "123" → "321".

Указанные классы содержат и другие методы, но они вряд ли пригодятся нам на практике. Однако при желании всегда можно обратиться к документации на https://docs.oracle.com/ или в IDEA, открыв соответствующие классы.


String

С классом String мы знакомы давно и, время от времени, узнаем о нем что-то новое.

Напомним, что String — неизменяем, а значит, при каждом изменении строки вынужден создавать новый объект. Таким образом, операции, изменяющие исходную строку в String будут дороже, чем в StringBuilder.

Из плюсов — у String есть пул строк, который позволяет переиспользовать строковые литералы. Но об этом ниже.

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

  • static join(). Позволяет объединять несколько строк в одну, расставляя между ними подстроки-разделители (разделитель передается как один из параметров). Имеет перегруженную реализацию, которая также позволяет установить в результирующей строке префикс и суффикс — подстроки в начало и конец результирующей строки одновременно;
  • static valueOf(). Возвращает строковое представление переданного параметра. С примитивными типами, полагаю, понятно. Для null вернет "null" (именно как строку), для объекта — вызовет toString() и вернет его результат. Именно этот метод используется внутри System.out.print();
  • static format(). Метод форматирования строк с использованием спецификаторов. Сигнатура такая же, как в известном нам System.out.printf(). Принцип действия тоже совпадает, только вместо вывода результирующей строки на консоль, она будет возвращена из метода. В Java 15 появился не статический метод с той же функциональностью: formatted(). Он более удобен в использовании;
  • split(). Принимает в качестве параметра разделитель. Возвращает массив строк, получившийся в результате разделения изначальной строки по переданному разделителю. Разделителем можно выступать как обычная строка, так и регулярное выражение. С механизмом регулярных выражений (regex, он же regexp) мы познакомимся в одном из ближайших уроков;
  • toLowerCase(). Приводит все символы строки к нижнему регистру (не буквенные символы остаются без изменений);
  • toUpperCase(). Приводит все символы строки к верхнему регистру (не буквенные символы остаются без изменений);
  • compareTo(). По логике работы аналогичен этому же методу в StringBuilder;
  • compareToIgnoreCase(). Полагаю, название говорит само за себя. Сравнивает строки, игнорируя регистр символов ("hi".compareToIgnoreCase("HI") == 0);
  • contains(). Проверяет, содержит ли исходная строка подстроку, переданную в качестве параметра;
  • intern(). Добавляет строку в пул строк, если она там отсутствует. Если уже существует — возвращает ссылку на эквивалентную строку из пула;
  • isBlank(). Проверяет, является ли строка пустой. В отличии от isEmpty() (он также доступен), isBlank() посчитает пустой и строку, состоящую только из пробелов;
  • matches(). Проверяет, соответствует ли исходная строка переданному регулярному выражению;
  • replace(). Заменяет все символы, аналогичные переданному как первый параметр на символы, аналогичные второму: "haha".replace('h', 'b').equals("baba"). Также есть перегруженный метод, принимающий в качестве параметров строки;
  • replaceAll(). Аналогичен replace() со строковыми параметрами. Но первым параметром можно передать регулярное выражение;
  • substring(). Возвращает подстроку по указанным в параметре индексам (или от указанного индекса до конца строки). Если у вас стоит JDK 6 или более ранние версии — рекомендую использовать с осторожностью, этот метод приводит к утечками памяти. В более поздних версиях проблема была исправлена;
  • toCharArray(). Представляет строку как массив символов, из которых она состоит. Порядок символов сохраняется;
  • strip(). Обрезает пробелы в начале и конце строки;
  • trim(). Похож на strip(). Только пробелом считает любой символ, с кодом <= 20.

Используя методы, изменяющие строку — помните, что эти методы вернут новый объект строки:

String str = " haha ";
str.trim();
System.out.println(str); //" haha "
-----------------------------------
String str = " haha ";
str = str.trim();
System.out.println(str); //"haha"

Также советую пока не особо обращать внимание на упоминание регулярных выражений. Но рекомендую вернуться к текущей статье, когда мы разберемся с ними. Это произойдет уже совсем скоро:)


Пул строк

Объекты String можно создать двумя способами: через литерал (String str = "sthStr") и через конструктор (String str = new String("sthStr")).

Первый способ использует пул строк (String pool) — область кучи, которая хранит существующие строковые литералы. Если такой литерал уже есть в пуле, будет взят он. Если нет — новый литерал будет добавлен в пул.

Второй же способ создаст новый объект String, даже если строка с таким значением уже есть в пуле. Но строку можно явно добавить в пул, вызвав для нее метод intern().

Пул строк — важный механизм, позволяющий использовать String намного более эффективно. Создание строк через конструктор — сомнительная практика, ее выгоды не очевидны. Не рекомендую использовать этот подход, поскольку он избыточен с точки зрения памяти.


С теорией на сегодня все!


Переходим к практике:

Задача 1:

Реализуйте задачу https://github.com/KFalcon2022/practical-tasks/blob/master/src/com/walking/lesson6_methods/Task3.java

используя StringBuilder или StringBuffer. Объясните свой выбор.


Задача 2:

Реализуйте программу, принимающую с клавиатуры строку, содержащую слова, разделенные пробелом. Выведите в консоль количество уникальных слов в строке. Учтите, что строка может начинаться с пробела.

Пример:

Мама мыла раму мама рамы мыла. Пример строки

В данном случае будем считать, что уникальных слов 6: мама, мыла, раму, рамы, пример, строки.

Для упрощения допустим, что в строке не могут использоваться символы, отличные от пробела или русских/английский букв. Помните, что слово может быть введено в разных регистрах.


Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)

Канал: https://t.me/+relA0-qlUYAxZjI6

Мой тг: https://t.me/ironicMotherfucker

 

Дорогу осилит идущий!

Report Page