Регулярные выражения: теперь и в Java

26 февраль, 2003 - 00:00Виктор Вейтман
Выбор конкретного инструмента -- несомненно один из самых важных этапов работы над проектом. От того, насколько удачно он сделан, не только зависят сроки выполнения, но и качество созданных программ. Нередко специфика некоторых языков делала их предпочтительными при решении определенного типа задач, однако со временем лучшие их свойства перенимаются другими, и шансы уравниваются.
Еще несколько лет назад ответ на вопрос о том, какая из технологий оптимальна для построения Web-приложения, был предопределен: альтернативы CGI попросту не существовало. Сейчас есть из чего выбирать. И если не сохранять неоправданную приверженность одному и тому же инструменту (будь то PHP, Perl или что-то другое), то принять правильное решение порой довольно трудно. Вполне возможно, например, что, отбросив заведомо неподходящие для конкретной задачи технологии, вы остановитесь перед выбором: CGI-сценарий на Perl или JSP-документ.

Нередко "последней песчинкой", перевешивавшей чашу весов в пользу Perl, оказывались мощные средства обработки текста, характерные для этого языка. Однако с недавнего времени необходимость сложной обработки текстовой информации уже не является препятствием для применения в Web-приложениях Java-сервлетов или JSP: в состав JDK 1.4 был включен пакет java.util. regex, содержащий классы Pattern и Matcher: первый -- представляет регулярное выражение, второй -- инкапсулирует последовательность символов, предназначенную для проверки. Впрочем, если регулярное выражение должно использоваться однократно, объекты можно и не создавать. Достаточно воспользоваться статическим методом matches() класса Pattern, который принимает выражение и обрабатываемую последовательность символов, а возвращает логическое значение, сообщающее о результатах проверки.

Если же предполагается применять регулярное выражение многократно, такой подход приведет к неоправданному расходу ресурсов. В этом случае целесообразно создать объекты Pattern и Matcher. Конструкторы для них не определены, поэтому для получения экземпляра Pattern используется статический метод compile() данного класса, а экземпляр Matcher формируется с помощью метода matcher() класса Pattern. Теперь с последовательностью символов, представленной экземпляром класса Matcher, можно выполнять самые разнообразные действия. Упоминавшийся выше метод matches(), на сей раз класса Matcher, дает возможность установить, отвечает ли вся последовательность символов регулярному выражению. В этом классе также определены find(), lookingAt(), appendReplacement(), replaceAll(), replaceFirst() и другие методы, позволяющие эффективно реализовать возможности поиска и замены, которыми славится язык Perl.

Если при построении регулярного выражения была допущена ошибка, генерируется исключительная ситуация. Соответствующий класс PatternSyntaxException также входит в состав пакета java.util.regex.

Последовательность символов, проверяемая на соответствие регулярному выражению, не обязательно должна представлять собой объект String. Методу matcher() класса Pattern, посредством которого создается экземпляр класса Matcher, в качестве параметра передается объект CharSequence. Помимо класса String, интерфейс java. lang.CharSequence реализует также классы StringBuffer и CharBuffer. Таким образом, поиск вхождений регулярных выражений можно производить непосредственно в буфере, используемом для операций ввода/вывода. Классы, предназначенные для поддержки буферов, и многие другие средства, реализующие расширенную функциональность, находятся в пакете java.nio, также впервые появившемся в JDK 1.4.

При более подробном изучении правил построения регулярных выражений обнаруживается, что они практически идентичны используемым в языке Perl. Благодаря этому многие конструкции можно непосредственно копировать из готовой Perl-программы в создаваемый сервлет или JSP-документ. Это важно, так как регулярные выражения часто бывают достаточно сложными; в частности, для решения такой, казалось бы, элементарной задачи, как описание формата почтового адреса, требуется выражение, насчитывающее более сотни символов. Однако полная идентичность по-прежнему остается недостижимым идеалом. Так, например, если в Perl, для того чтобы возобновить поиск после обнаружения первого соответствия, необходимо указывать флаг g, то в Java глобальный поиск подразумевается по умолчанию.

Поддержка регулярных выражений -- не единственный "шаг навстречу Perl", предпринятый разработчиками Java. В JDK 1.4 в классе String реализован метод split(), выполняющий те же действия, что и одноименная функция языка Perl. В частности, он возвращает массив String[] с результатом разбиения строки, если в роли разделителя выступает набор символов (кстати, это может быть и регулярное выражение), переданный в качестве параметра. Опять же, допустимо обрабатывать не только строку, содержащуюся в объекте String, но и любую последовательность символов, представленную одним из классов, реализуемых интерфейсом CharSequence. Для этого также используется метод split(), но реализованный уже в классе Pattern. При вызове ему передается только последовательность, предназначенная для разбиения, а разделителем считается выражение, инкапсулированное в текущем объекте.

Таким образом, благодаря новым средствам, включенным в состав JDK 1.4, больше нет причины отказываться от повторного использования разработанных ранее Java-классов лишь оттого, что для решения задачи требуется сложная обработка текста.