Ответ 1
Из вашего отредактированного примера я теперь могу видеть, что вы хотите. И у вас тоже есть мои симпатии. Регулярные выражения Javas - это длинный, длинный и длинный путь от удобства, которое вы найдете на языках программирования более высокого уровня, таких как Ruby или Perl. И они почти всегда будут; это не может быть исправлено, поэтому навсегда застряли в этом беспорядке - по крайней мере, на Java. Другие языки JVM лучше справляются с этим, особенно Groovy. Но они по-прежнему страдают некоторыми из присущих ему недостатков и могут только зайти.
С чего начать? Существуют так называемые методы удобства класса String: matches
, replaceAll
, replaceFirst
и split
. Иногда они могут быть одобрены в небольших программах, в зависимости от того, как вы их используете. Однако у них действительно есть несколько проблем, которые, как вам кажется, вы обнаружили. Это неполный список этих проблем, и что можно и не может сделать с ними.
-
Метод неудобства очень странно назван "совпадением", но он требует, чтобы вы поместили ваше регулярное выражение с обеих сторон, чтобы соответствовать всей строке. Этот контр-интуитивный смысл противоречит любому пониманию соответствия слова, используемого на любом предыдущем языке, и постоянно укусывает людей. Шаблоны, переданные в другие 3 неудобства, работают очень в отличие от этого, потому что в других 3 они работают, как обычные шаблоны, работают повсюду; просто не в
matches
. Это означает, что вы не можете просто скопировать свои шаблоны вокруг, даже в рамках методов в том же проклятом классе ради добра! И нет никакого методаfind
, чтобы делать то, что делает каждый другой помощник в мире. Методmatches
должен был называться чем-то вродеFullMatch
, и в класс String должен был быть добавлен методPartialMatch
илиfind
. -
Нет API, который позволяет передавать флаги
Pattern.compile
вместе со строками, которые вы используете для 4 удобных методов, связанных с шаблонами класса String. Это означает, что вам следует полагаться на строковые версии, такие как(?i)
и(?x)
, но они не существуют для всех возможных флагов компиляции шаблонов. Это, по меньшей мере, неудобно. -
Метод
split
не возвращает тот же результат в случаях краев, что иsplit
возвращается на языках, от которых заимствован Java. Это непродуманная маленькая добыча. Сколько элементов, по вашему мнению, нужно вернуть в возвратный список, если вы разделите пустую строку, а? Java-разработчики представляют собой поддельный элемент возврата, где должен быть один, что означает, что вы не можете различать законные результаты и фиктивные. Это серьезный дефект дизайна, который разбивается на":"
, вы не можете определить разницу между входами""
vs":"
. Ой, да! Разве люди никогда не проверяют этот материал? И опять же, нарушение и принципиально ненадежное поведение неустойчиво: вы никогда не должны менять вещи, даже сломать вещи. Нехорошо сломать сломанные вещи в Java, так как это где-то еще. Сломанный навсегда здесь. -
Обозначение обратных косов с регулярными выражениями конфликтует с символом обратной косой черты, используемым в строках. Это делает его супердуперу неудобным и склонным к ошибкам, потому что вам приходится постоянно добавлять много обратных косых ко всем, а его слишком легко забыть и не получить ни предупреждения, ни успеха. Простые шаблоны, такие как
\b\w+\b
, становятся кошмарами в типографском избытке:"\\b\\w+\\b"
. Удачи вам в чтении. Некоторые люди используют функцию слэш-инвертора на своих шаблонах, чтобы они могли записать это как"/b/w+/b"
. Помимо чтения в ваших шаблонах из строки, нет возможности построить ваш шаблон в WYSIWYG буквально; его всегда тяжело нагружены с обратной косой чертой. Вы получили их все, и достаточно, и в нужных местах? Если это так, очень трудно читать. Если это не так, вы, вероятно, не получили их всех. По крайней мере, языки JVM, такие как Groovy, нашли правильный ответ здесь: дайте людям регулярные выражения 1-го класса, чтобы вы не сходили с ума. Вот справедливая коллекция примеров Groovy regex, показывающая, насколько это просто и возможно. -
Режим
(?x)
имеет глубокие недостатки. Он не принимает комментарии в стиле Java// COMMENT
, а скорее в стиле оболочки# COMMENT
. Он не работает с многострочными строками. Он не принимает литералы как литералы, вызывая проблемы с обратной косой чертой, перечисленные выше, что в корне компрометирует любую попытку выстроить вещи, например, все комментарии начинаются в одной колонке. Из-за обратных косых черт вы либо заставляете их начинать в том же столбце в строке исходного кода, и прикручивать их, если вы распечатываете их, или наоборот. Так много для удобочитаемости! -
Это невероятно сложно - и в самом деле, принципиально неустойчиво сломано - ввести символы Unicode в регулярное выражение. Нет поддержки символически названных символов, таких как
\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}]
, потому что компилятор regex закручивает UTF-16. Опять же, это никогда не может быть исправлено или оно изменит старые программы. Вы даже не можете обойти ошибку, используя обычное обходное решение проблем Javas Unicode в исходном коде, компилируя с помощьюjava -encoding UTF-8
, потому что глупая вещь хранит строки как неприятные UTF-16, что обязательно ломает их в классах символов. OOPS! -
Многие из элементов регулярных выражений, которые мы используем на других языках, отсутствуют на Java. Для примеров нет ни названных групп, ни даже относительно пронумерованных. Это делает построение более крупных моделей из меньших, в основном, подверженных ошибкам. Существует интерфейсная библиотека, которая позволяет вам иметь простые именованные группы, и действительно, это, наконец, придет в производство JDK7. Но даже в этом случае нет механизма для того, что делать с более чем одной группой с тем же именем. И вы все еще не имеете относительно пронумерованных буферов. Вернулись в "Бад-старые дни" снова, что было решено эоны назад.
-
Нет поддержки последовательности строк, которая является одной из двух "сильно рекомендованных" частей стандарта, что предполагает, что для этого используется
\R
. Это неудобно подражать из-за своей переменной длины, а у Джаваса нет поддержки графем. -
Экраны класса символов не работают в наборе символов Javas! Да, это правильно: обычные вещи вроде
\w
и\s
(вернее,"\\w"
и"\\b"
) не работают в Unicode в Java! Это не крутой ретро. Хуже того, Javas\b
(сделать, что"\\b"
, который не совпадает с"\b"
), имеет некоторую чувствительность Unicode, хотя не то, что стандарт говорит, что это должно быть. Так, например, строка, подобная"élève"
, никогда не будет в Java соответствовать шаблону\b\w+\b
, а не только целиком наPattern.matches
, но действительно без каких-либо проблем, как вы могли бы получить изPattern.find
. Это просто так испорчено, как нищая вера. Они нарушили неотъемлемую связь между\w
и\b
, а затем неправильно определили их для загрузки! Он даже не знает, что такое Unicode Алфавитный код. Это в высшей степени нарушено, и они никогда не смогут его исправить, потому что это изменит поведение существующего кода, который строго запрещен в Java Universe. Лучшее, что вы можете сделать, это создать библиотеку перезаписи, которая будет выступать в качестве интерфейса перед тем, как перейти к фазе компиляции; таким образом, вы можете принудительно перенести свои шаблоны с 1960-х годов на 21-й век обработки текста. -
Поддерживаются только два свойства Unicode: общие категории и свойства блока. Свойства общей категории поддерживают только аббревиатуры типа
\p{Sk}
, в отличие от стандартов "Сильная рекомендация", чтобы также разрешать\p{Modifier Symbol}
,\p{Modifier_Symbol}
и т.д. Вы даже не получаете требуемые псевдонимы, которые, как утверждают стандарты, вам нужно. Это делает ваш код еще более нечитаемым и неподъемным. Наконец, вы получите поддержку свойства Script в производстве JDK7, но это все еще серьезно не соответствует минимальному набору из 11 основных свойств, которые Стандарт говорит, что вы должны предоставить даже минимальный уровень поддержки Unicode. -
Некоторые из слабых свойств, которые предоставляет Java, являются faux amis: у них есть те же имена, что и официальные имена поддержки Unicode, , но они делают что-то совсем другое. Например, для Unicode требуется, чтобы
\p{alpha}
был таким же, как\p{Alphabetic}
, но Java делает его только архаичным и не более длинным 7-битным алфавитом, что на 4 порядка меньше. Пробел - еще один недостаток, так как вы используете версию Java, которая маскируется как пробелы в Unicode, ваши партизаны UTF-8 будут ломаться из-за своих кодовых точек NO-BREAK SPACE, которые Unicode нормативно требует, чтобы их считали пробелами, но Java игнорирует это требование, поэтому перерывы ваш парсер. -
Нет никакой поддержки графемам, как обычно предоставляет
\X
. Это делает невозможным неисчислимое множество общих задач, которые вам нужны и которые вы хотите делать с регулярными выражениями. Не только расширенные кластеры grapheme недоступны, потому что Java почти не поддерживает свойства Unicode, вы даже не можете приблизить старый кластеры наследия grapheme с использованием стандартного(?:\p{Grapheme_Base}\p{Grapheme_Extend}]*)
. Невозможность работать с графемами делает невозможными даже самые простые виды обработки текста в Юникоде. Например, вы не можете сопоставить гласную, независимо от диакритики на Java. Способ, которым вы это делаете на языке с поддержкой графемы, различается, но по крайней мере вы должны быть в состоянии выбросить вещь в NFD и соответствовать(?:(?=[aeiou])\X)
. На Java вы не можете этого сделать: графемы недоступны. И это означает, что Java не может даже обрабатывать собственный собственный набор символов. Он дает вам Unicode, а затем делает невозможным работу с ним. -
Методы удобства в классе String не кэшируют скомпилированное регулярное выражение. На самом деле нет такой вещи, как шаблон времени компиляции, который проверяется синтаксисом во время компиляции - , когда предполагается синтаксическая проверка. Это означает, что ваша программа, которая использует только константу регулярные выражения, полностью понятые во время компиляции, будут выбиваться с ошибкой в середине ее прогона, если вы забудете немного обратную косую черту здесь или там, как это обычно делается из-за недостатков, которые обсуждались ранее. Даже Groovy получает эту часть права. Регулярные выражения представляют собой слишком высокоуровневую конструкцию, с которой Джавас сталкивается с неприятной моделью с фактом, с болтами на стороне, и они слишком важны для рутинной обработки текста, которую нужно игнорировать. Java - это слишком низкоуровневый язык для этого материала, и он не может обеспечить простую механику, из которой вы можете сами построить то, что вам нужно: вы не можете добраться туда отсюда.
-
Классы
String
иPattern
помечены какfinal
в Java. Это полностью уничтожает любую возможность использования надлежащего дизайна OO для расширения этих классов. Вы не можете создать лучшую версию методаmatches
путем подкласса и замены. Черт, ты не можешь даже подкласс! Финал не является решением; final - смертный приговор, из которого нет апелляции.
Наконец, чтобы показать вам, насколько серьезно поврежденные мозгом Javas действительно представляют собой регулярные выражения, рассмотрите этот многострочный шаблон, который показывает многие из описанных выше недостатков:
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 вам запрещены, потому что все это заперто с окончательностью смертного приговора, и оно не может быть исправлено.
Итак, Alireza Noori, если вы чувствуете, что Javas неуклюжие регулярные выражения слишком запущены для надежной и удобной обработки регулярных выражений, которые когда-либо были возможны на Java, я не могу вас разочаровать. Извините, но так оно и есть.
"Исправлено в следующей версии!"
Просто потому, что некоторые вещи никогда не могут быть исправлены, не означает, что ничто никогда не может быть исправлено. Это нужно сделать очень осторожно. Вот те вещи, о которых я знаю, которые уже исправлены в текущих JDK7 или предлагаемых сборках JDK8:
-
Теперь поддерживается свойство Unicode Script. Вы можете использовать любую из эквивалентных форм
\p{Script=Greek}
,\p{sc=Greek}
,\p{IsGreek}
или\p{Greek}
. Это по своей сути превосходит старые неуклюжие свойства блока. Это означает, что вы можете делать такие вещи, как[\p{Latin}\p{Common}\p{Inherited}]
, что очень важно. -
Ошибка UTF-16 имеет обходное решение. Теперь вы можете указать любую кодовую точку Юникода по ее номеру с помощью обозначения
\x{⋯}
, например\x{1D402}
. Это работает даже внутри классов символов, что позволяет[\x{1D400}-\x{1D419}]
работать правильно. Вы все равно должны удвоить обратную косую черту, но это работает только в регулярном выражении, а не в строках вообще, как это действительно должно быть. -
Именованные группы теперь поддерживаются через стандартную нотацию
(?<NAME>⋯)
для ее создания, а\k<NAME>
- для обратной ссылки. Они по-прежнему вносят вклад в числовые номера групп. Однако вы не можете получить более одного из них в одном шаблоне и не можете использовать их для рекурсии. -
Новый флаг компиляции шаблона
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 Regular Expresions" , без которого вы действительно не можете работать с Unicode,
Эти улучшения и исправления очень важны, чтобы, наконец, иметь, и поэтому я рад, даже взволнован, чтобы иметь их.
Но для промышленной прочности, состояния регулярных выражений и/или Unicode я не буду использовать Java. Theres слишком много пропавших без вести в Javas, все еще - пятнадцатилетняя модель Unicode, чтобы получить реальную работу, если вы решитесь использовать набор символов, который дает Java. И модель с болтовым соединением никогда не работает, и это все регулярные выражения Java. Вы должны начать с первых принципов, как это делал Groovy.
Конечно, это может работать для очень ограниченных приложений, чья небольшая клиентская база ограничена англоязычными монологами в сельской Айове без каких-либо внешних взаимодействий или какой-либо необходимости в персонажах, помимо того, что мог отправить телеграф старого образца. Но для каких проектов это действительно так? Меньше, чем вы думаете, оказывается.
Именно по этой причине определенный (и очевидный) многомиллиардный доллар недавно отменял международное развертывание важного приложения. Поддержка Javas Unicode - не только в регулярных выражениях, но и во всем - оказалась слишком слабой, чтобы необходимая интернационализация была надежно выполнена на Java. Из-за этого они были вынуждены отступить от своего первоначально запланированного развертывания по всему миру до простого развертывания в США. Его позитивный приход. И нет, есть Nᴏᴛ Hᴀᴘᴘʏ; вы были бы?
У Java было 20 лет, чтобы понять это, и они явно не сделали этого до сих пор, поэтому я бы не затаил дыхание. Или бросить хорошие деньги после неудачи; урок здесь состоит в том, чтобы игнорировать ажиотаж и вместо этого применять должную осмотрительность, чтобы быть уверенным, что вся необходимая инфраструктурная поддержка существует до того, как вы инвестируете слишком много. В противном случае вы тоже можете застрять без каких-либо реальных вариантов, как только вы окажетесь слишком далеко, чтобы спасти свой проект.