如何添加Java正则表达式实现中缺少的功能?

Ali*_*ori 25 java regex

我是Java的新手.作为一名.Net开发人员,我非常习惯Regex.Net中的课程.Regex(正则表达式)的Java实现并不错,但它缺少一些关键功能.

我想为Java创建自己的帮助器类,但我想可能已经有一个可用.那么在Java中是否有可用于Regex的免费且易于使用的产品,或者我应该自己创建一个?

如果我会写自己的课程,你认为我应该在哪里分享它以供其他人使用?


[编辑]

有人抱怨说我没有解决当前Regex班级的问题.我会试着澄清我的问题.

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


这是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);
}
Run Code Online (Sandbox Code Playgroud)

在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()));
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

在更高级的用法中,差异显示出更多.再看方法Groups,辞典长度Capture,Index,Length,Success,等等,这些都是非常必要的功能,在我看来应该是可用于Java太.

当然,所有这些功能都可以通过自定义代理(帮助程序)类手动添加.这是我问这个问题的主要原因.我们没有RegexPerl 的微风,但至少我们可以使用Regex我认为非常巧妙设计的.Net方法.

tch*_*ist 111

从您编辑的示例中,我现在可以看到您想要的内容.你也对此表示同情.从Ruby或Perl中找到的便利开始,Java的正则表是很长很长的路.他们几乎总是如此; 这是无法修复的,所以我们永远陷入这种混乱 - 至少在Java中.其他JVM语言在这方面做得更好,尤其是Groovy.但它们仍然存在一些固有的缺陷,而且只能走得那么远.

从哪里开始?有String类的所谓的便利方法:matches,replaceAll,replaceFirst,和split.根据您使用它们的方式,这些在小程序中有时可以正常使用.但是,它们确实存在一些问题,您似乎已经发现了这些问题.以下是这些问题的部分清单,以及可以和不可以做些什么.

  1. 不方便的方法非常奇怪地命名为"匹配",但它要求您在两侧填充正则表达式以匹配整个字符串.这种反直觉的感觉与任何以前的语言中使用的任何单词匹配感相反,并且不断地咬人.传递给其他3种不便方法的模式与此不同,因为在其他3种方法中,它们的工作方式与其他地方的普通模式相同; 只是不在matches.这意味着你不能只是复制你的模式,即使是在同一个darned类中的方法中,为了善意!并且没有find方便的方法来做世界上每个其他匹配器所做的事情.matches应该调用该方法FullMatch,并且应该在String类中添加一个PartialMatchfind方法.

  2. 没有API允许您传入Pattern.compile标记以及用于String类的4个与模式相关的便捷方法的字符串.这意味着你很可能依赖像(?i)和的字符串版本(?x),但是对于所有可能的Pattern编译标志都不存在.至少可以说这是非常不方便的.

  3. split在边缘情况下,该方法不会返回与splitJava借用的语言中的返回相同的结果.这是一个偷偷摸摸的小骗子.如果拆分空字符串,认为应该在返回列表中返回多少个元素,是吗?Java制造商应该有一个假的返回元素,这意味着你无法区分合法的结果和虚假的结果.这是一个严重的设计缺陷,分裂为a ":",你无法分辨""vs的输入之间的区别":".噢,哎!人们不测试这些东西吗?再一次,破碎和根本不可靠的行为是不可修复的:你必须永远不要改变东西,甚至是破碎的东西.用Java来破坏破碎的东西是不行的.破碎永远在这里.

  4. 正则表达式的反斜杠表示法与字符串中使用的反斜杠表示法冲突.这使得它超级笨拙,而且容易出错,因为你必须经常向所有东西添加大量的反斜杠,而且很容易忘记一个并且既没有警告也没有成功.简单的模式就像\b\w+\b印刷过剩的噩梦一样:"\\b\\w+\\b".祝你好运.有些人在他们的模式上使用斜线反转功能,以便他们可以将其写为"/b/w+/b"相反.除了从字符串中读取模式之外,没有办法以所见即所得的文字方式构建模式; 它总是装满了反斜杠.你把它们全部,足够,并在正确的地方得到了吗?如果是这样,那真的很难读.如果不是,你可能还没有得到它们.至少像Groovy这样的JVM语言在这里找到了正确的答案:为人们提供一流的正则表达式让你不要疯狂.这是一个Groovy正则表达式示例的公平集合,显示它可以和应该是多么简单.

  5. 这种(?x)模式存在严重缺陷.它没有采用Java风格的注释,// COMMENT而是采用shell风格# COMMENT.它不适用于多行字符串.它不接受文字作为文字,强制上面列出的反斜杠问题,这从根本上损害了任何排除内容的尝试,比如让所有评论都在同一列上开始.由于反斜杠,您可以让它们从源代码字符串中的同一列开始,如果打印出来则将它们搞砸,反之亦然.太可靠了!

  6. 在正则表达式中输入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}]因为正则表达式编译器搞砸了UTF-16.同样,这永远不会被修复或它将改变旧程序.你甚至无法通过使用正常的解决方法来解决这个问题java -encoding UTF-8,因为愚蠢的东西将字符串存储为令人讨厌的UTF-16,它必然会在字符类中将它们分解,因此使用Java的Unicode-in-source-code麻烦. OOPS!

  7. Java中缺少许多我们在其他语言中依赖的正则表达式.示例中没有命名组,甚至也没有相对编号的组.这使得从较小的模式构建较大的模式从根本上是容易出错的.有一个前端库,允许您拥有简单的命名组,实际上这将最终到达生产JDK7.但即便如此,也没有机制可以使用同一个名称来处理多个组.而且你仍然没有相对编号的缓冲区.我们又回到了Bad Old Days,这是以前解决过的问题.

  8. 没有支持换行序列,这是该标准中仅有的两个"强烈推荐"部分之一,这表明\R可以用于此类.由于其可变长度性质以及Java缺乏对字形的支持,因此难以模拟.

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

  10. 支持的唯一两个Unicode属性是"常规类别"和"块"属性.一般的类别属性只支持类的缩写\p{Sk},违背了标准的强烈推荐中,也允许\p{Modifier Symbol},\p{Modifier_Symbol}等你甚至不得到所需要的别名标准说,你应该.这使您的代码更难以理解且无法维护.您最终将获得生产JDK7中Script属性的支持,但这仍然严重缺少标准所说的必须提供的11个基本属性的最小集合,即使是最低级别的Unicode支持也必须提供.

  11. Java提供的一些微不足道的属性是虚假的amis:它们与官方的Unicode propoperty名称具有相同的名称,但它们完全不同.例如,Unicode要求\p{alpha}与之相同\p{Alphabetic},但Java使其成为古老且不再古雅的7位字母,仅超过4个数量级.空白是另一个缺陷,因为你使用伪装成Unicode空格的Java版本,你的UTF-8解析器会因为它们的NO-BREAK SPACE代码点而中断,Unicode规范性地要求将其视为空格,但是Java忽略了这个要求,所以中断你的解析器.

  12. \X通常提供的方式不支持字素.这使得你需要和想要使用正则表达式完成许多常见任务.不仅扩展的字形集群超出了您的范围,因为Java几乎不支持任何Unicode属性,您甚至无法使用该标准来近似旧的旧字体集群(?:\p{Grapheme_Base}\p{Grapheme_Extend}]*).不能使用字形使得即使是最简单的Unicode文本处理也是不可能的.例如,无论Java中的变音符号如何,都无法匹配元音.你在使用字形支持的语言中这样做的方式各不相同,但至少你应该能够把东西扔进NFD并匹配(?:(?=[aeiou])\X).在Java中,你不能做那么多:字素是你无法企及的.这意味着Java甚至无法处理自己的原生字符集.它为您提供了Unicode,然后无法使用它.

  13. String类中的便捷方法不会缓存已编译的正则表达式.实际上,没有编译时模式可以在编译时进行语法检查 - 这就是应该进行语法检查的时候.这意味着你的程序只使用在编译时完全理解的常量正则表达式,如果你在这里或那里忘记了一点反斜杠,那么会因为前面讨论的缺陷而忽略了一个例外. .即便是Groovy也能胜任这一部分.正则规则是一个非常高级别的构造,需要由Java不愉快的事后处理模型来处理 - 而且它们对于常规文本处理而言非常重要.Java对于这些东西来说太低级了,它无法提供简单的机制,你自己可以构建你需要的东西:你无法从这里获得.

  14. StringPattern类被标记final在Java中.这完全杀死了使用适当的OO设计扩展这些类的任何可能性.您无法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"
        ;
Run Code Online (Sandbox Code Playgroud)

你觉得这有多自然吗?你必须在字符串中加入文字换行符; 你必须使用非Java注释; 由于额外的反斜杠,你不能排队; 你必须使用对Unicode不起作用的东西的定义.除此之外还有很多问题.

不仅没有计划解决几乎任何这些严重的缺陷,因此你几乎无法修复它们中的任何一个,因为你改变了旧的程序.即使是OO设计的常规工具也是禁止的,因为它们都被判处死刑判决的终结,而且无法修复.

所以阿里努里,如果你觉得Java的正则表达式笨拙过于大清洗可靠,便捷的正则表达式处理以往在Java中是可能的,我不能反驳你.对不起,但就是这样.

"已在下一版中修复!"

仅仅因为有些东西永远无法修复并不意味着什么都无法修复.它必须非常谨慎地完成.以下是我所知道的已经在当前JDK7或建议的JDK8版本中修复的内容:

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

  2. UTF-16错误有一个解决方法.您现在可以使用\x{?}符号指定任何Unicode代码点,例如\x{1D402}.这甚至可以在角色类中使用,最终允许[\x{1D400}-\x{1D419}]正常工作.你仍然必须加倍反斜杠,它只适用于regexex,而不是一般的字符串,因为它应该.

  3. 现在,通过标准符号支持命名组(?<NAME>?)来创建它并对其\k<NAME>进行反向引用.这些仍然有助于数字组编号.但是,您不能以相同的模式获取多个,也不能将它们用于递归.

  4. 一种新的模式编译标志,Pattern.UNICODE_CHARACTER_CLASSES以及相关的嵌入式交换机(?U),现在将交换的东西像身边所有的定义\w,\b,\p{alpha},和\p{punct},使他们现在符合的Unicode标准所要求的那些东西的定义.

  5. 丢失或misdefined二元性\p{IsLowercase},\p{IsUppercase}\p{IsAlphabetic}现在得到支持,并且这些对应于方法Character类.这很重要,因为Unicode在单纯的字母和套接字或字母代码点之间进行了重要且普遍的区分.这些关键属性属于第1级符合UTS#18,"Unicode常规表达"绝对必需的11个基本属性,没有这些属性,您实际上无法使用Unicode.

这些增强和修复对于最终拥有非常重要,所以我很高兴,甚至兴奋,拥有它们.

但对于工业级,最先进的正则表达式和/或Unicode工作,我不会使用Java.如果您敢于使用Java提供的字符集,那么在Java的20年后仍然不完整的Unicode模型中,有太多的缺失可以让您真正完成工作.并且螺栓式的模型永远不会起作用,这是所有Java正则表达式.你必须从第一原则开始,就像Groovy那样.

当然,它可能适用于非常有限的应用程序,其小客户群仅限于爱荷华州农村地区的单一语言,没有外部交互或者除了旧式电报之外还需要任何字符.但对于有多少项目是是真的吗?事实证明,即使你认为也很少.

正是出于这个原因,一个(并且显而易见的)数十亿美元最近取消了一项重要申请的国际部署.Java的Unicode支持 - 不仅在正则表达式中,而且在整个过程中 - 被证明太弱,无法在Java中可靠地完成所需的国际化.正因为如此,他们被迫从最初计划的全域部署缩减到仅仅是美国部署.这是非常狭隘的.不,有NᴏᴛHᴀᴘᴘʏ; 你会吗?

Java已经有20年的时间才能做到正确,而且他们到目前为止还没有这样做过,所以我不会屏住呼吸.或者在糟糕的情况下抛出好钱; 这里的教训是忽略炒作,而是适用尽职调查做出非常确保所有必要的基础设施支持,是有之前你投入太多.否则,一旦你太过于拯救你的项目,你也可能会在没有任何实际选择的情况下陷入困境.

买者自负

  • +1哇.我知道有一个原因我从来没有花时间学习Java.现在回去研究下一版[你的书](http://www.amazon.com/Programming-Perl-3rd-Larry-Wall/dp/0596000278/"Programmin Perl"),这样我就可以去了买它,阅读它并一劳永逸地学习Perl!(这么多语言,所以很少的时间......) (4认同)
  • @Alireza:就像所有使用Java正则表达式而非玩具应用程序的人一样,我也用Java编写了自己的前端库来解决其中的一些问题.问题是它们中的很多只能在编译器本身中解决,这就是Groovy的做法.我建议你用你的前端课程尽你所能(太糟糕了,你不能使用OO!).祝好运! (3认同)
  • 起初看起来这篇帖子似乎没有解决我的问题,但是我读的答案越多,我就越有乐趣阅读它.非常复杂的答案和是的,它回答了我的问题.感谢所有的信息.在阅读你的答案之前我不知道这些问题中的一半(或者我必须说文章);) (2认同)

Ali*_*ael 10

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

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

}
Run Code Online (Sandbox Code Playgroud)

根据需要使用:

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);
       }
    }
}
Run Code Online (Sandbox Code Playgroud)