如何添加Java正则expression式实现中缺less的function?

我是Java新手。 作为一个.Net开发者,我非常习惯.Net中的Regex类。 Regex (正则expression式)的Java实现并不差,但缺less一些关键特性。

我想为Java创build自己的帮助类,但我想也许已经有一个可用的。 那么有没有免费的,易于使用的产品在Java正则expression式或我应该自己创build一个?

如果我要写自己的课,你认为我应该把它分享给别人来使用它?


[编辑]

有人抱怨说,我没有用当前的Regex类来解决这个问题。 我会尽力澄清我的问题。

在.Net中,正则expression式的使用比在Java中更容易。 由于这两种语言都是面向对象的,而且在很多方面都非常相似,所以我期望在两种语言中使用正则expression式都有类似的经验。 不幸的是,事实并非如此。


这里有一些比较Java和C#的代码。 第一个是C#,第二个是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())); } 

我试图在上面的示例代码中对这两种语言公平。

您在这里注意到的第一件事是Match类的.Value成员(与在Java中使用.start().end()相比)。

为什么当我可以调用像Regex.MatchesRegex.Match等静态函数时,我应该创build两个对象?

在更先进的用法中,差别更大。 看看方法Groups ,字典长度, CaptureIndexLengthSuccess等。这些都是非常必要的function,在我看来也应该可用于Java。

当然,所有这些function都可以通过自定义代理(帮助程序)类手动添加。 这是我问这个问题的主要原因。 我们在Perl中没有Regex的风,但至less我们可以使用.Net方法来Regex ,我认为它非常巧妙。

从你编辑的例子中,我现在可以看到你想要的。 而且你也对此表示同情。 Java的正则expression式从您在Ruby或Perl等高级编程语言中find的方便程度来说,是一个漫长而漫长的过程。 他们几乎总是会; 这个问题不能解决,所以我们永远陷在这个混乱中 – 至less在Java中。 其他JVM语言在这方面做得更好,特别是Groovy。 但他们仍然有一些固有的缺陷,只能走得这么远。

从哪里开始? 有所谓的String类的便捷方法: matchesreplaceAllreplaceFirstsplit 。 这些有时可以在小程序中确定,具体取决于你如何使用它们。 但是,他们的确有几个问题,看起来你已经发现了。 以下是这些问题的部分列表,以及关于这些问题可以做什么和不可以做什么。

  1. 不方便的方法是非常奇怪的命名为“匹配”,但它需要你填充你的正则expression式在两侧匹配整个string。 这种违反直觉的观点与任何以前的语言中使用的单词匹配的任何意义相反,并且不断地咬人。 传入其他三种不便方法的模式与此不一样,因为在其他三种方式中,它们像其他地方一样正常工作。 只是不matches 。 这意味着你不能只是复制你的模式,即使是在同一个类的方法,为善! 而且没有find方便的方法去做世界上其他所有的匹配者。 matches方法应该被称为像FullMatch ,应该有一个PartialMatchfind方法添加到String类。

  2. 没有API允许您将Pattern.compile标志与用于String类的4个与模式相关的便捷方法的string一起传递。 这意味着你需要依赖像(?i)(?x)这样的string版本,但是对于所有可能的模式编译标志,这些并不存在。 这至less可以说是非常不方便的。

  3. split方法在边缘情况下不会像Java借用的语言中的split返回一样返回相同的结果。 这是一个鬼鬼祟祟的小问题。 如果分割空string, 认为应该返回多less个元素? Java制造商应该有一个假的返回元素,这意味着你不能区分合法的结果和假的结果。 这是一个严重的devise缺陷,分裂在一个":" ,你不能区分""":"input之间的区别。 哦,哎呀! 难道人们不会testing这个东西吗? 再次,破碎的,根本不可靠的行为是不可修复的:你不能改变事情,甚至是破碎的事情。 在Java中打破破碎的东西是不正确的。 破碎永远在这里。

  4. 正则expression式的反斜杠符号与string中使用的反斜杠符号冲突。 这使得它非常笨拙,也容易出错,因为你必须不断增加大量反斜杠到一切,而且很容易忘记一个,既不会警告也不会成功。 像\b\w+\b这样的简单模式在排印过度中变成噩梦: "\\b\\w+\\b" 。 祝你好运,阅读。 有些人在他们的模式上使用斜杠逆变器function,以便他们可以将其写为"/b/w+/b" 。 除了从string中读取模式外,没有办法以所见即所得的文字方式来构build模式。 它总是带有反斜杠。 你把他们全都拿了,够了,在正确的地方? 如果是这样,它真的很难阅读。 如果不是的话,你可能还没有得到全部。 至less像Groovy这样的JVM语言已经在这里find了正确的答案:给人们一等正则expression式,所以你不要疯狂。 这里有一个公平的Groovy正则expression式示例,展示了它可以和应该是多么简单。

  5. (?x)模式有很深的缺陷。 它不会在// COMMENT的Java风格中进行// COMMENT ,而是在// COMMENT的shell风格中进行# COMMENT 。 它不适用于多行string。 它不接受文字作为文字,强制上面列出的反斜杠问题,这从根本上危及任何排队的尝试,如所有的评论开始在同一列。 由于反斜杠,你可以让它们在源代码string中的同一列开始,如果你打印出来就把它们拧紧,反之亦然。 这么多的易读性!

  6. 在正则expression式中inputUnicode字符是非常困难的 – 事实上,从根本上说是不可修复的。 不支持符号命名的字符,例如\N{QUOTATION MARK}\N{LATIN SMALL LETTER E WITH GRAVE}\N{MATHEMATICAL BOLD CAPITAL C} \N{LATIN SMALL LETTER E WITH GRAVE} \N{MATHEMATICAL BOLD CAPITAL C} 。 这意味着你陷入了难以维系的幻数。 而且你甚至无法通过代码点input它们。 您不能使用\u0022作为第一个,因为Java预处理器会导致语法错误。 那么你就转到了CANON_EQ 最后一个是纯粹的噩梦:它的代码点是U + 1D402,但是Java不支持在正则expression式中使用它们的代码点数完整的Unicode集合,迫使你拿出你的计算器来找出那是\uD835\uDC02\\uD835\\uDC02 (但不是\\uD835\uDC02 ), \\uD835\uDC02了。 但是由于devise缺陷,你不能在字符类中使用这些类,因为正则expression式编译器在UTF-8上绑定了这个string,所以不能匹配[\N{MATHEMATICAL BOLD CAPITAL A}-\N{MATHEMATICAL BOLD CAPITAL Z}]大写字母[\N{MATHEMATICAL BOLD CAPITAL A}-\N{MATHEMATICAL BOLD CAPITAL Z}]大写字母[\N{MATHEMATICAL BOLD CAPITAL A}-\N{MATHEMATICAL BOLD CAPITAL Z}] 16。 再次,这是永远不能修复的,否则会改变旧的程序。 通过使用java -encoding UTF-8进行编译,通过使用Java的Unicode内源代码问题的常规解决方法,您甚至java -encoding UTF-8该问题,因为愚蠢的事情将string存储为令人讨厌的UTF-16,人物类。 OOPS!

  7. Java中缺less许多我们在其他语言中依赖的正则expression式。 例子中没有命名组,甚至没有相对编号的组。 这使得从较小的模式构造更大的模式从根本上是错误的。 有一个前端库,允许你有简单的命名组,事实上这将最终到达生产JDK7。 但是,即便如此,也不存在如何处理同一名称的多个组织的机制。 而且你还没有相对编号的缓冲区。 我们又一次回到了糟糕的老日子,这是以前解决的问题。

  8. 没有支持换行顺序,这是标准中唯一的两个“强烈推荐”部分之一,这表明\R被用于这样的。 由于其变长的性质以及Java对字形缺乏支持,这样做是尴尬的。

  9. 字符类转义不适用于Java的本地字符集! 是的,这是正确的:例如像\w\s (或者更确切地说, "\\w""\\b" )在Java中不适用于Unicode! 这不是一个很酷的复古。 更糟糕的是,Java的\b (使"\\b""\b" )具有一些Unicode的敏感性,尽pipe不是标准所说的。 例如,像"élève"这样的string在Java中绝不会匹配模式\b\w+\b ,而不是每个Pattern.matches完全匹配,但实际上并不Pattern.find 。 对于乞丐的信仰,这是如此搞砸了。 他们已经打破了\w\b之间的内在联系,然后误将它们定义为启动! 它甚至不知道什么Unicode字母代码点。 这是至关重要的,他们永远不能修复它,因为这将改变现有代码的行为,这是Java Universe中严格禁止的。 你可以做的最好的是创build一个重写库,在它进入编译阶段之前充当前端; 这样你就可以将你的模式从20世纪60年代强行迁移到21世纪的文本处理。

  10. 唯一支持的两个Unicode属性是常规类别和块属性。 一般类别属性只支持像\p{Sk}这样的缩写,与标准的强大build议相反,它也允许\p{Modifier Symbol}\p{Modifier_Symbol}等等。甚至不需要标准的别名说你应该。 这使得你的代码更加难以理解和不可维护。 您将最终获得对生产JDK7中脚本属性的支持,但是仍然严重短于Standard的11个基本属性的最小集合,即使是最低级别的Unicode支持也必须提供。

  11. Java所提供的一些微不足道的属性是虚假的 :它们与官方的Unicode属性名称具有相同的名称, 但它们完全不同 。 例如,Unicode要求\p{alpha}\p{Alphabetic} ,但是Java使得它只是古代的和不再古怪的7位字母,这比4个数量级less得多。 Whitespace是另一个缺陷,因为你使用伪装成Unicode空格的Java版本,你的UTF-8parsing器会因为它们的NO-BREAK SPACE代码点而中断,这个Unicode规范地要求被认为是空白的,但是Java忽略了这个要求,你的parsing器。

  12. 字母\X通常提供的方式不支持字形。 这使得无数不可能完成许多你需要和正则expression式需要处理的常见任务。 由于Java几乎不支持任何Unicode属性,因此您甚至不能使用标准(?:\p{Grapheme_Base}\p{Grapheme_Extend}]*)来近似旧的传统字形集群 。 不能使用字形使得即使是最简单的Unicode文本处理也是不可能的。 例如,你不能匹配一个元音,而不考虑Java中的变音符号。 你用一个支持字形的语言来做这件事的方式各不相同,但至less你应该能够把这个东西扔进NFD并匹配(?:(?=[aeiou])\X) 。 在Java中,你甚至无法做到这一点:字形是无法实现的。 这意味着Java甚至无法处理自己的本地字符集。 它给你的Unicode,然后使它不可能工作。

  13. String类中的便捷方法不会caching已编译的正则expression式。 实际上,在编译时就没有这样的编译时模式, 即在语法检查应该发生时进行语法检查。 这意味着你的程序只使用编译时完全理解的常量正则expression式,如果你在这里或者那里忘记了一个反斜杠,那么在运行过程中就会发生exception,因为之前讨论过的缺陷。 即使Groovy也能正确使用这个部分。 正则expression式是一个高层次的构造,由Java的不愉快的事后,用螺栓固定的模型来处理,而且它们对于常规的文本处理来说太重要了,以致于被忽略。 Java对于这个东西来说太低层次了,它不能提供简单的机制,你可以根据这些机制创build你所需要的东西:你不能从这里得到它。

  14. StringPattern类在Java中被标记为final 。 这完全杀死了使用适当的OOdevise来扩展这些类的任何可能性。 您不能通过inheritance和replace来创build更好的matches方法版本。 哎呀,你甚至不能inheritance子类! 决赛并不是一个解决scheme; 最后是一个没有上诉的死刑。

最后,为了向您展示Java真正的正则expression式是如何受到脑损坏的,请考虑这种多行模式,该模式显示了许多已经描述的缺陷:

  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" . " | LLXX # 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" ; 

你看到这是多么不自然吗? 你必须把你的string中的字面换行符; 你必须使用非Java评论; 由于多余的反斜杠,你不能做任何事情。 你必须使用在Unicode上不适用的东西的定义。 除此之外还有更多的问题。

不但没有计划去解决几​​乎所有这些严重的缺陷,根本不可能修复它们中的任何一个,因为你改变了旧的程序。 即使是面向对象devise的常用工具也是被禁止的,因为它全部被locking在死刑的终结点上,而且无法修复。

所以Alireza Noori,如果你觉得Java的笨拙的正则expression式对于Java中的可靠和便捷的正则expression式处理来说太过于沉重,我不能够赞扬你。 对不起,但这只是它的方式。

“在下一个版本中修复!”

仅仅因为有些东西永远不能解决,并不意味着什么都不能解决。 这只是要非常小心地做。 以下是我所知道的在当前的JDK7或build议的JDK8版本中已经修复的内容:

  1. 现在支持Unicode脚本属性。 您可以使用任何等效forms\p{Script=Greek}\p{sc=Greek}\p{IsGreek}\p{Greek} 。 这本质上优于旧笨重的块性质。 这意味着你可以做一些事情,比如[\p{Latin}\p{Common}\p{Inherited}] ,这非常重要。

  2. UTF-16错误有一个解决方法。 您现在可以使用\x{⋯}表示法来指定任何Unicode代码点,例如\x{1D402} 。 这甚至可以在angular色类内部工作,最终允许[\x{1D400}-\x{1D419}]正常工作。 你仍然必须加倍反斜杠,它只适用于正则expression式,而不是一般的string,因为它真的应该。

  3. 命名组现在通过标准符号(?<NAME>⋯)来支持创build它,而\k<NAME>可以反向引用它。 这些仍然有助于数字组号码。 然而,你不能以相同的模式获取其中的一个以上,也不能使用它们进行recursion。

  4. 一个新的模式编译标志Pattern.UNICODE_CHARACTER_CLASSES和相关的可embedded开关(?U)现在将交换\w\b\p{alpha}\p{punct}类的所有定义,这样他们现在符合Unicode标准所要求的那些东西的定义 。

  5. 现在将支持缺less的或错误定义的二元属性\p{IsLowercase}\p{IsUppercase}\p{IsAlphabetic} ,这些对应于Character类中的方法。 这一点很重要,因为Unicode对于单纯的字母和封底或字母代码点进行了重大和普遍的区分。 这些关键属性是11个基本属性之一,对于符合UTS#18“Unicode Regular Expresions”的1级标准是绝对必需的,没有这些属性,您实际上无法使用Unicode。

这些增强和修复对于终于有了非常重要的东西,所以我很高兴甚至兴奋地拥有它们。

但是,对于工业强度,最先进的正则expression式和/或Unicode工作,我不会使用Java。 如果您敢于使用Java提供的字符集,那么在Java的20年后的Unicode模型中仍然存在太多的缺失,无法完成真正的工作。 而螺栓连接的模型从来没有工作,这是所有的Java正则expression式。 你必须从最初的原则开始,Groovy的方式。

当然,它可能适用于非常有限的应用程序,其小客户群仅限于爱荷华州的农村地区,没有任何外部交互,也不需要任何超出旧式电报发送的字符。 但是有多less项目真的是真的? 事实certificate,即使你这么想,

正因为如此,一个(而且是显而易见的)数十亿美元刚刚取消了一个重要应用的国际部署。 Java的Unicode支持 – 不仅在正则expression式中,而且在整个过程中 – 被certificate对于在Java中可靠地完成所需的国际化来说太弱了。 正因为如此,他们不得不从原来计划的全域部署缩减到仅仅在美国的部署。 这是积极的狭隘。 不,有NᴏᴛHᴀᴘᴘʏ; 你会吗?

爪哇已经有20年的时间了,到目前为止他们还没有这样做,所以我不会屏住呼吸。 或者在糟糕之后投入好的钱; 这里的教训是忽略炒作,而是应用尽职调查,确保所有必要的基础设施支持都您投入太多之前 。 否则,你也可能陷入困境,没有任何真正的select,一旦你太过分了,以挽救你的项目。

买者自负

人们可以咆哮,或者可以简单地写:

 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); } } } 

男孩,我听到你在那个Alireza! 正则expression式的混淆不够,没有太多的语法变体。 我也比Java编程做了更多的C#并且有同样的问题。

我发现这是非常有用的: http : //www.tusker.org/regex/regex_benchmark.html – 这是基于Java的备用正则expression式实现列表。

@ tchrist的答案中提到的一些API漏洞在Kotlin中是固定的。