Как добавить функции, отсутствующие в реализации Java Regex?
Я новичок в Java. Как разработчик.Net, я очень привык к Regex
класс в.Net. Java-реализация Regex
(Регулярные выражения) неплохо, но в нем отсутствуют некоторые ключевые функции.
Я хотел создать свой собственный вспомогательный класс для Java, но подумал, что, возможно, он уже есть. Так есть ли какой-нибудь бесплатный и простой в использовании продукт, доступный для Regex на Java, или я должен создать его сам?
Если бы я написал свой собственный класс, как вы думаете, где я должен поделиться им, чтобы другие могли его использовать?
[Редактировать]
Были жалобы, что я не решал проблему с текущим Regex
учебный класс. Я постараюсь уточнить мой вопрос.
В.Net использование регулярных выражений проще, чем в Java. Поскольку оба языка являются объектно-ориентированными и очень похожими во многих аспектах, я ожидаю, что у меня будет одинаковый опыт использования регулярных выражений в обоих языках. К сожалению, это не так.
Here's a little code compared in Java and C#. The first is C# and the second is Java:
В C#:
string source = "The colour of my bag matches the color of my shirt!";
string pattern = "colou?r";
foreach(Match match in Regex.Matches(source, pattern))
{
Console.WriteLine(match.Value);
}
В Java:
String source = "The colour of my bag matches the color of my shirt!";
String pattern = "colou?r";
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(source);
while(m.find())
{
System.out.println(source.substring(m.start(), m.end()));
}
I tried to be fair to both languages in the sample code above.
The first thing you notice here is the .Value
член Match
class (compared to using .start()
а также .end()
на Яве).
Why should I create two objects when I can call a static function like Regex.Matches
или же Regex.Match
, так далее.?
In more advanced usages, the difference shows itself much more. Посмотрите на метод Groups
, dictionary length, Capture
, Index
, Length
, Success
, etc. These are all very necessary features that in my opinion should be available for Java too.
Of course all of these features can be manually added by a custom proxy (helper) class. This is main reason why I asked this question. We don't have the breeze of Regex
in Perl but at least we can use the.Net approach to Regex
which I think is very cleverly designed.
5 ответов
Из вашего отредактированного примера теперь я вижу, что вы хотели бы. И у вас есть мои симпатии в этом тоже. Регулярные выражения Java - это длинный, длинный и длинный путь от удобства, которое вы найдете в языках программирования более высокого уровня, таких как Ruby или Perl. И они почти всегда будут; это не может быть исправлено, поэтому мы застряли с этим беспорядком навсегда - по крайней мере, в Java. Другие языки JVM лучше справляются с этой задачей, особенно Groovy. Но они все еще страдают от некоторых присущих им недостатков и могут зайти так далеко.
С чего начать? Существуют так называемые вспомогательные методы класса String: matches
, replaceAll
, replaceFirst
, а также split
, Иногда это может быть хорошо в небольших программах, в зависимости от того, как вы их используете. Тем не менее, у них действительно есть несколько проблем, которые, по-видимому, вы обнаружили. Вот неполный список этих проблем, и что можно и что нельзя делать с ними.
Метод неудобства очень странно называют "совпадением", но он требует, чтобы вы добавили регулярное выражение с обеих сторон, чтобы соответствовать всей строке. Этот нелогичный смысл противоречит любому значению слова "совпадение", используемому на любом предыдущем языке, и постоянно кусает людей. Шаблоны, переданные в другие 3 метода неудобств, работают очень непохоже на этот, потому что в остальных 3 они работают так же, как нормальные шаблоны, работают везде; просто не в
matches
, Это означает, что вы не можете просто копировать свои шаблоны, даже внутри методов в том же проклятом классе, ради всего святого! И нетfind
удобный метод, чтобы делать то, что делает любой другой матч в мире.matches
метод должен был называться что-то вродеFullMatch
и должен был бытьPartialMatch
или жеfind
метод добавлен в класс String.Там нет API, который позволяет передавать в
Pattern.compile
flags вместе со строками, которые вы используете для 4 связанных с шаблоном вспомогательных методов класса String. Это означает, что вы должны полагаться на строковые версии, такие как(?i)
а также(?x)
, но они не существуют для всех возможных флагов компиляции Pattern. Это очень неудобно, если не сказать больше.split
метод не возвращает тот же результат в крайних случаях, какsplit
возвращает на языках, которые Java позаимствовал Это подлый маленький гоча. Как вы думаете, сколько элементов вы должны вернуть в список возврата, если вы разбили пустую строку, а? Java производит поддельный возвращаемый элемент там, где он должен быть, что означает, что вы не можете отличить законные результаты от поддельных. Это серьезный недостаток дизайна, который расщепляется на":"
, вы не можете сказать разницу между входами""
против":"
, Оу, ну и дела! Разве люди никогда не тестируют это? И опять же, сломанное и принципиально ненадежное поведение невозможно исправить: вы никогда не должны ничего менять, даже сломанные. Нельзя разбивать сломанные вещи в Java, как это нигде. Сломанный навсегда здесь.Нотация обратного слэша регулярных выражений конфликтует с нотацией обратного слэша, используемой в строках. Это делает его сверхпопулярным неловким и подверженным ошибкам, потому что вы должны постоянно добавлять множество обратных косых черт ко всему, и слишком легко забыть один и не получить ни предупреждения, ни успеха. Простые шаблоны, такие как
\b\w+\b
стать кошмарами в типографском избытке:"\\b\\w+\\b"
, Удачи с чтением этого. Некоторые люди используют функцию косой черты в своих шаблонах, чтобы они могли записать это как"/b/w+/b"
вместо. Кроме чтения в ваших шаблонах из строки, нет никакого способа построить ваш шаблон в буквальном смысле WYSIWYG; это всегда тяжело с обратной косой чертой. Вы получили их все, и достаточно, и в нужных местах? Если так, то это действительно очень трудно читать. Если это не так, вы, вероятно, не получили их всех. По крайней мере, языки JVM, такие как Groovy, нашли здесь правильный ответ: дайте людям регулярные выражения первого класса, чтобы вы не сходили с ума. Вот большая коллекция примеров регулярных выражений Groovy, показывающих, насколько простым это может и должно быть.(?x)
Режим глубоко испорчен. Это не принимает комментарии в стиле Java// COMMENT
а скорее в стиле оболочки# COMMENT
, Это не работает с многострочными строками. Он не принимает литералы как литералы, что вызывает проблемы с обратной косой чертой, перечисленные выше, что в корне компрометирует любую попытку выстраивать ряды, например, когда все комментарии начинаются в одном столбце. Из-за обратной косой черты, вы либо заставляете их начинаться с того же столбца в строке исходного кода, а затем перепутываете их, если распечатываете, или наоборот. Так много для разборчивости!Невероятно сложно - и действительно, принципиально не поддающееся нарушению - вводить символы Юникода в регулярном выражении. Нет поддержки для символов с символическими именами, таких как
\N{QUOTATION MARK}
,\N{LATIN SMALL LETTER E WITH GRAVE}
, или же\N{MATHEMATICAL BOLD CAPITAL C}
, Это означает, что вы застряли с неуправляемыми магическими числами. И вы даже не можете ввести их по коду. Вы не можете использовать\u0022
для первого, потому что препроцессор Java делает это синтаксической ошибкой. Итак, вы переходите к\\u0022
вместо этого, который работает, пока вы не доберетесь до следующего,\\u00E8
, который не может быть введен таким образом, или это сломаетCANON_EQ
флаг. И последний из них - настоящий кошмар: его кодовая точка U+1D402, но Java не поддерживает полный набор Unicode, использующий их номера кодовых точек в регулярных выражениях, что вынуждает вас использовать калькулятор, чтобы выяснить, что это\uD835\uDC02
или же\\uD835\\uDC02
(но нет\\uD835\uDC02
), достаточно безумно. Но вы не можете использовать их в классах персонажей из-за ошибки проектирования, что делает невозможным сопоставление, скажем,[\N{MATHEMATICAL BOLD CAPITAL A}-\N{MATHEMATICAL BOLD CAPITAL Z}]
потому что компилятор регулярных выражений испортил UTF-16. Опять же, это никогда не может быть исправлено, или это изменит старые программы. Вы даже не можете обойти ошибку, используя обычный обходной путь для проблем Unicode-in-source-кода Java, компилируя сjava -encoding UTF-8
потому что глупая вещь хранит строки как неприятный UTF-16, который обязательно разбивает их в классах символов. OOPS!Многие из регулярных выражений, которые мы привыкли использовать в других языках, отсутствуют в Java. Для примеров нет ни именованных групп, ни даже относительно пронумерованных. Это делает построение больших шаблонов из меньших по своей сути подверженным ошибкам. Существует интерфейсная библиотека, которая позволяет вам иметь простые именованные группы, и, действительно, это, наконец, появится в рабочей JDK7. Но даже в этом случае нет механизма для того, что делать с более чем одной группой с одним и тем же именем. И у вас все еще нет относительно пронумерованных буферов. Мы снова вернулись к плохим старым дням, вещи, которые были решены давным-давно.
Не поддерживается последовательность разрыва строки, которая является одной из двух "строго рекомендуемых" частей стандарта, что предполагает, что
\R
быть использованы для таких. Это неудобно для подражания из-за природы переменной длины и отсутствия поддержки графами в Java.Экранирование класса символов не работает с собственным набором символов Java! Да, все верно: рутинные вещи вроде
\w
а также\s
(или скорее,"\\w"
а также"\\b"
) не работает на Unicode в Java! Это не крутой вид ретро. Что еще хуже, Java\b
(сделать это"\\b"
, который не совпадает с"\b"
) имеет некоторую чувствительность к Unicode, хотя не то, что стандарт должен сказать, что он должен иметь. Так, например, строка как"élève"
никогда не будет в Java соответствовать шаблону\b\w+\b
и не только полностью вPattern.matches
, но на самом деле ни в коем случае, как вы могли бы получить отPattern.find
, Это просто так облажалось, что нельзя верить. Они нарушили внутреннюю связь между\w
а также\b
, а затем неправильно определил их для загрузки! Он даже не знает, что такое буквенные коды Unicode. Это в высшей степени нарушено, и они никогда не смогут это исправить, потому что это изменит поведение существующего кода, что строго запрещено во Вселенной Java. Лучшее, что вы можете сделать, - это создать библиотеку перезаписи, которая будет работать в качестве внешнего интерфейса, прежде чем перейдет к фазе компиляции; Таким образом, вы можете принудительно перенести ваши шаблоны из 1960-х в 21-й век обработки текста.Поддерживаются только два свойства Юникода: общие категории и свойства блока. Свойства общей категории поддерживают только такие сокращения, как
\p{Sk}
вопреки стандартам Сильная Рекомендация также позволяет\p{Modifier Symbol}
,\p{Modifier_Symbol}
и т. д. Вы даже не получите требуемых псевдонимов, которые стандарт должен указывать. Это делает ваш код еще более нечитаемым и не поддерживаемым. Наконец, вы получите поддержку свойства Script в производственном JDK7, но это все еще серьезно не соответствует минимальному набору из 11 основных свойств, которые, как сказано в стандарте, должны быть предусмотрены даже для минимального уровня поддержки Unicode.Некоторые из скудных свойств, которые предоставляет Java, являются ложными: они имеют те же имена, что и официальные имена свойств Unicode, но они делают что-то совершенно другое. Например, Unicode требует, чтобы
\p{alpha}
быть таким же, как\p{Alphabetic}
, но Java делает его архаичным и более не причудливым 7-битным алфавитом, что на 4 порядка меньше. Пробелы - это еще один недостаток, так как вы используете версию Java, которая маскируется под пробел Unicode, ваши парсеры UTF-8 будут ломаться из-за их кодовых точек NO-BREAK SPACE, которые Unicode нормативно требуют, чтобы считаться пробелами, но Java игнорирует это требование, поэтому нарушает твой парсер.Там нет поддержки графем, способ
\X
обычно обеспечивает. Это делает невозможным бесчисленное множество общих задач, которые вам нужны и которые вы хотите выполнять с помощью регулярных выражений. Мало того, что расширенные кластеры графем недоступны, поскольку Java не поддерживает почти ни одно из свойств Unicode, вы даже не можете аппроксимировать старые устаревшие кластеры графем, используя стандартные(?:\p{Grapheme_Base}\p{Grapheme_Extend}]*)
, Неспособность работать с графемами делает невозможной даже простейшую обработку текста в Юникоде. Например, вы не можете найти гласный независимо от диакритического знака в Java. То, как вы делаете это на языке с поддержкой графем, варьируется, но по крайней мере вы должны быть в состоянии бросить это в NFD и сопоставить(?:(?=[aeiou])\X)
, В Java вы не можете сделать даже так много: графемы за пределами вашей досягаемости. А это значит, что Java не может даже обрабатывать свой собственный набор символов. Он дает вам Unicode, а затем делает невозможным работу с ним.Вспомогательные методы в классе String не кэшируют скомпилированное регулярное выражение. На самом деле, нет такой вещи, как шаблон во время компиляции, который проверяется на синтаксис во время компиляции - то есть, когда предполагается, что проверка синтаксиса происходит. Это означает, что ваша программа, которая использует только постоянные регулярные выражения, полностью понятные во время компиляции, сработает с исключением в середине выполнения, если вы забудете небольшую обратную косую черту здесь или там, как обычно, из-за ранее рассмотренных недостатков, Даже Groovy правильно понимает эту часть. Регулярные выражения - это слишком высокоуровневая конструкция, чтобы иметь дело с неприятной постфактумной моделью Java, скрытой от фактов, - и они слишком важны для обычной обработки текста, чтобы их игнорировать. Java - слишком низкоуровневый язык для этого материала, и он не в состоянии предоставить простую механику, из которой вы сами можете построить то, что вам нужно: вы не можете получить это отсюда.
String
а такжеPattern
классы отмеченыfinal
на Яве. Это полностью убивает любую возможность использования правильного дизайна ОО для расширения этих классов. Вы не можете создать лучшую версиюmatches
метод подклассов и замены. Черт возьми, ты не можешь даже подкласс! Финал не является решением; окончательный - смертный приговор, к которому нет апелляции.
Наконец, чтобы показать вам, насколько на самом деле являются регулярные выражения поврежденного Java, рассмотрим этот многострочный шаблон, который показывает многие из уже описанных недостатков:
String rx =
"(?= ^ \\p{Lu} [_\\pL\\pM\\d\\-] + \$)\n"
. " # next is a big can't-have set \n"
. "(?! ^ .* \n"
. " (?: ^ \\d+ $ \n"
. " | ^ \\p{Lu} - \\p{Lu} $ \n"
. " | Invitrogen \n"
. " | Clontech \n"
. " | L-L-X-X # dashes ok \n"
. " | Sarstedt \n"
. " | Roche \n"
. " | Beckman \n"
. " | Bayer \n"
. " ) # end alternatives \n"
. " \\b # only on a word boundary \n"
. ") # end negated lookahead \n"
;
Вы видите, как это неестественно? Вы должны поместить буквальные переводы строк в свои строки; вы должны использовать не Java-комментарии; вы не можете сделать что-либо из-за дополнительных обратных слешей; Вы должны использовать определения вещей, которые не работают правильно на Unicode. Помимо этого есть еще много проблем.
Мало того, что не планируется исправлять почти любые из этих серьезных недостатков, на самом деле практически невозможно исправить почти любые из них, потому что вы меняете старые программы. Даже обычные инструменты OO-дизайна запрещены, потому что все они связаны с окончанием смертного приговора и не могут быть исправлены.
Итак, Алиреза Нури, если вы чувствуете, что неуклюжие регулярные выражения Java слишком сложны, чтобы надежная и удобная обработка регулярных выражений когда-либо была возможна в Java, я не могу отрицать вас. Извините, но так оно и есть.
"Исправлено в следующей версии!"
То, что некоторые вещи никогда не могут быть исправлены, не означает, что ничто не может быть исправлено. Это нужно сделать очень осторожно. Вот вещи, которые я знаю, которые уже исправлены в текущей JDK7 или предлагаемых сборках JDK8:
Свойство Unicode Script теперь поддерживается. Вы можете использовать любую из эквивалентных форм
\p{Script=Greek}
,\p{sc=Greek}
,\p{IsGreek}
, или же\p{Greek}
, Это по своей сути превосходит старые неуклюжие свойства блока. Это означает, что вы можете делать такие вещи, как[\p{Latin}\p{Common}\p{Inherited}]
, что довольно важно.У ошибки UTF-16 есть обходной путь. Теперь вы можете указать любую кодовую точку Unicode по ее номеру, используя
\x{⋯}
нотация, такая как\x{1D402}
, Это работает даже внутри классов персонажей, наконец, позволяя[\x{1D400}-\x{1D419}]
работать правильно. Вы все еще должны удвоить обратную косую черту, хотя, и это работает только в regexex, а не в строках в целом, как должно.Именованные группы теперь поддерживаются через стандартную запись
(?<NAME>⋯)
создать его и\k<NAME>
чтобы сослаться на это. Они по-прежнему вносят свой вклад в числовые номера групп. Однако вы не можете получить более одного из них по одному шаблону, а также не можете использовать их для рекурсии.Новый флаг компиляции Pattern,
Pattern.UNICODE_CHARACTER_CLASSES
и связанный встраиваемый переключатель,(?U)
, теперь будут обмениваться всеми определениями таких вещей, как\w
,\b
,\p{alpha}
, а также\p{punct}
, так что теперь они соответствуют определениям тех вещей, которые требуются в стандарте Unicode.Отсутствующие или неправильно определенные двоичные свойства
\p{IsLowercase}
,\p{IsUppercase}
, а также\p{IsAlphabetic}
будет поддерживаться, и они соответствуют методам вCharacter
учебный класс. Это важно, потому что Unicode делает существенное и распространенное различие между простыми буквами и буквенными или буквенными кодовыми точками. Эти ключевые свойства входят в число 11 основных свойств, которые абсолютно необходимы для соответствия Уровню 1 UTS#18 "Регулярные выражения Unicode", без которого вы действительно не сможете работать с Unicode.
Эти улучшения и исправления очень важны, чтобы их иметь, и поэтому я рад, даже рад, что они у меня есть.
Но я не буду использовать Java для промышленного, современного использования регулярных выражений и / или Unicode. Просто слишком много не хватает в модели Unicode, которая все еще неуклюжа по прошествии 20 лет, чтобы выполнить реальную работу, если вы решитесь использовать набор символов, который дает Java. И модель на болтах никогда не работает, как и все Java-регулярные выражения. Вы должны начать с первых принципов, как это сделал Groovy.
Конечно, это может работать для очень ограниченных приложений, чья небольшая клиентская база ограничена англоязычными моноглотами в сельской Айове без внешних взаимодействий или какой-либо потребности в символах, помимо того, что мог послать телеграф старого стиля. Но для скольких проектов это действительно так? Оказывается, даже меньше, чем вы думаете.
Именно по этой причине определенный (и очевидный) многомиллиардный доллар недавно отменил международное развертывание важного приложения. Поддержка Unicode в Java - не только в регулярных выражениях, но и повсюду - оказалась слишком слабой для необходимой надежной интернационализации в Java. Из-за этого они были вынуждены перейти от первоначально запланированного развертывания по всему миру к просто развертыванию в США. Это положительно местничество. И нет, есть Nᴏᴛ Hᴀᴘᴘʏ; Вы были бы?
У Java было 20 лет, чтобы сделать это правильно, и они, очевидно, еще не сделали этого, поэтому я не мог задержать дыхание. Или бросать хорошие деньги за плохими; урок здесь состоит в том, чтобы игнорировать ажиотаж и вместо этого применить должную осмотрительность, чтобы убедиться, что вся необходимая инфраструктурная поддержка есть, прежде чем вкладывать слишком много. В противном случае вы тоже можете застрять без каких-либо реальных опций, если вы слишком далеко в этом, чтобы спасти свой проект.
Пусть покупатель будет бдителен
Можно рутовать или просто написать:
public class Regex {
/**
* @param source
* the string to scan
* @param pattern
* the regular expression to scan for
* @return the matched
*/
public static Iterable<String> matches(final String source, final String pattern) {
final Pattern p = Pattern.compile(pattern);
final Matcher m = p.matcher(source);
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
@Override
public boolean hasNext() {
return m.find();
}
@Override
public String next() {
return source.substring(m.start(), m.end());
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
}
Используется по вашему желанию:
public class RegexTest {
@Test
public void test() {
String source = "The colour of my bag matches the color of my shirt!";
String pattern = "colou?r";
for (String match : Regex.matches(source, pattern)) {
System.out.println(match);
}
}
}
Некоторые из недостатков API, упомянутых в ответе @tchrist, были исправлены в Kotlin.
Мальчик, я слышу тебя об этом Алиреза! Регулярные выражения достаточно запутаны, так как среди них не так много синтаксических вариаций. Я тоже делаю намного больше C#, чем программирование на Java, и у меня была та же проблема.
Я обнаружил, что это очень полезно: http://www.tusker.org/regex/regex_benchmark.html- это список альтернативных реализаций регулярных выражений для Java, сравнительный тест.
Это чертовски хорошо, если я сам так скажу! регулярное выражение-тестер-инструмент