从您编辑的示例中,我现在可以看到您想要的内容。在这方面,你也有我的同情。Java的正则表达式距离Ruby或Perl中的便利性还有很长的路要走。他们几乎总是这样;这是无法修复的,所以我们永远被困在这个混乱中 - 至少在Java中。其他JVM语言在这方面做得更好,尤其是Groovy。但他们仍然遭受一些固有的缺陷,只能走这么远。
从哪里开始?有 String 类的所谓方便方法:、 、 、 和 。这些有时在小程序中是可以的,这取决于你如何使用它们。但是,他们确实有几个问题,看来您已经发现了这些问题。以下是这些问题的部分列表,以及可以和不能对它们做些什么。matches
replaceAll
replaceFirst
split
-
不便的方法被非常奇怪地命名为“matches”,但它需要您在两侧填充正则表达式以匹配整个字符串。这种反直觉的含义与以前任何语言中使用的“匹配”这个词的任何含义相反,并且不断咬人。传递给其他3种不便方法的模式的工作方式与此方法非常不同,因为在其他3种方法中,它们的工作方式与其他任何地方的正常模式一样;只是不在.这意味着你不能只是复制你的模式,即使是在同一个的类中的方法中,为了善良的缘故!而且没有方便的方法可以做世界上所有其他匹配器所做的事情。该方法应该被称为 类似 ,并且应该有一个 or 方法添加到 String 类中。matches
find
matches
FullMatch
PartialMatch
find
-
没有 API 允许您传入标志以及用于 String 类的 4 个与模式相关的便利方法的字符串。这意味着您必须依赖像 和 这样的字符串版本,但对于所有可能的 Pattern 编译标志,这些版本并不存在。至少可以说这是非常不方便的。Pattern.compile
(?i)
(?x)
-
该方法在边缘情况下返回的结果与 Java 借用拆分的语言中的返回结果不同。这是一个偷偷摸摸的小问题。如果您拆分空字符串,您认为应该在返回列表中返回多少个元素,嗯?Java制造了一个假的返回元素,其中应该有一个,这意味着您无法区分合法结果和虚假结果。这是一个严重的设计缺陷,在 上拆分,您无法区分 的输入与 的。啊,哎呀!人们从来没有测试过这些东西吗?再说一遍,破碎的、从根本上不可靠的行为是无法修复的:你永远不能改变事物,即使是破碎的事物。在Java中打破破碎的东西是不行的,就像在其他任何地方一样。破碎永远在这里。split
split
":"
""
":"
-
正则表达式的反斜杠表示法与字符串中使用的反斜杠表示法冲突。这使得它变得超级笨拙,也容易出错,因为你必须不断地向所有内容添加大量反斜杠,而且很容易忘记一个,既没有得到警告,也没有得到成功。简单的模式,如成为印刷过剩的噩梦:.祝你好运。有些人在他们的模式上使用斜杠逆变器功能,这样他们就可以把它写成这样。除了从字符串中读取模式外,没有办法以所见即所得的字面方式构建您的模式;它总是充满反斜杠。您是否在正确的地方得到了它们,并且已经足够了?如果是这样,它真的很难阅读。如果不是,您可能还没有全部得到它们。至少像Groovy这样的JVM语言已经在这里找到了正确的答案:给人们一等正则表达式,这样你就不会发疯了。这是一个公平的Groovy正则表达式示例集合,展示了它可以和应该是多么简单。\b\w+\b
"\\b\\w+\\b"
"/b/w+/b"
-
该模式存在严重缺陷。它不接受 Java 样式的注释,而是 shell 样式的 。它不适用于多行字符串。它不接受文字作为文字,强制上面列出的反斜杠问题,这从根本上损害了任何排列事情的尝试,比如让所有评论都在同一列上开始。由于反斜杠,您可以使它们从源代码字符串中的同一列开始,并在打印出来时将它们搞砸,反之亦然。这么多的易读性!(?x)
// COMMENT
# COMMENT
-
在正则表达式中输入 Unicode 字符是非常困难的 - 实际上,从根本上说是不可修复的。不支持以符号命名的字符,如 、 或 。这意味着你被困在无法维护的幻数中。您甚至无法通过码位输入它们。不能用于第一个,因为 Java 预处理器会将其设置为语法错误。因此,然后您移动到,这在您到达下一个之前一直有效,该方法无法以这种方式输入,否则它将破坏标志。最后一个是纯粹的噩梦:它的代码点是U + 1D402,但是Java不支持使用正则表达式中的码位编号的完整Unicode集,迫使你拿出计算器来弄清楚这是或(但不是),疯狂地。但是由于设计错误,您无法在字符类中使用它们,从而无法匹配,因为正则表达式编译器在UTF-16上搞砸了。同样,这永远无法修复,否则它将更改旧程序。您甚至无法通过使用 Java 的 Unicode 源代码问题(通过编译 )来解决该错误,因为愚蠢的事情是将字符串存储为令人讨厌的 UTF-16,这必然会在字符类中破坏它们。哎呀!\N{QUOTATION MARK}
\N{LATIN SMALL LETTER E WITH GRAVE}
\N{MATHEMATICAL BOLD CAPITAL C}
\u0022
\\u0022
\\u00E8
CANON_EQ
\uD835\uDC02
\\uD835\\uDC02
\\uD835\uDC02
[\N{MATHEMATICAL BOLD CAPITAL A}-\N{MATHEMATICAL BOLD CAPITAL Z}]
java -encoding UTF-8
-
我们在其他语言中依赖的许多正则表达式的东西在Java中都缺失了。例如,没有命名组,甚至没有相对编号的组。这使得从较小的模式中构建较大的模式从根本上容易出错。有一个前端库,允许您拥有简单的命名组,实际上这最终将到达生产版JDK7。但即便如此,也没有机制可以处理多个同名的组。而且您仍然没有相对编号的缓冲区。我们又回到了糟糕的旧时代,那些很久以前就已经解决了的事情。
-
不支持换行序列,这是标准中仅有的两个“强烈建议”部分之一,这表明应该用于此类。这很难模仿,因为它的可变长度性质和Java缺乏对字素的支持。\R
-
字符类转义不适用于 Java 的本机字符集!是的,没错:像 and(或者更确切地说,和 )这样的常规内容在 Java 中的 Unicode 上不起作用!这不是那种很酷的复古。更糟糕的是,Java的(使那个,这与)确实有一些Unicode敏感性,尽管不是标准所说的它必须具有的。例如,像 Java 中的字符串永远不会匹配该模式,并且不仅仅是整个 per ,而且实际上永远不会像您可能从中得到的那样。这简直是搞砸了,以至于乞丐信仰。它们已经破坏了 和 之间的固有联系,然后错误地将它们定义为引导!!它甚至不知道Unicode字母代码点是什么。这是非常破碎的,他们永远无法修复它,因为这会改变现有代码的行为,这在Java宇宙中是严格禁止的。你能做的最好的事情就是创建一个重写库,在它进入编译阶段之前充当前端;通过这种方式,您可以强行将模式从 20 世纪 60 年代迁移到 21 世纪的文本处理。\w
\s
"\\w"
"\\b"
\b
"\\b"
"\b"
"élève"
\b\w+\b
Pattern.matches
Pattern.find
\w
\b
-
唯一支持的两个 Unicode 属性是“常规类别”和“块”属性。一般类别属性仅支持缩写,如 ,与标准相反,强烈建议也允许 , 等。您甚至无法获得标准规定的所需别名。这会使您的代码更加不可读和不可维护。您最终将获得对生产版 JDK7 中 Script 属性的支持,但这仍然严重低于标准规定您必须提供哪怕是最低级别的 Unicode 支持的 11 个基本属性的最小集合。\p{Sk}
\p{Modifier Symbol}
\p{Modifier_Symbol}
-
Java确实提供的一些微薄的属性是假的:它们与官方Unicode专有名称具有相同的名称,但它们所做的完全不同。例如,Unicode 要求它与 相同,但 Java 仅将其设置为过时且不再古怪的 7 位字母,这比 4 个数量级少得多。空格是另一个缺陷,因为您使用的Java版本伪装成Unicode空格,您的UTF-8解析器将因其NO-BREAK空格代码点而中断,Unicode规范地要求将其视为空格,但Java忽略了该要求,因此会破坏您的解析器。\p{alpha}
\p{Alphabetic}
-
不支持字素,这是通常提供的方式。这使得您需要和想要使用正则表达式执行的无数常见任务变得不可能。扩展的字素簇不仅超出了您的范围,因为Java几乎不支持Unicode属性,您甚至无法使用标准来近似旧的遗留字素簇。由于无法使用字素,即使是最简单的 Unicode 文本处理也是不可能的。例如,在 Java 中,无论音调符号如何,您都不能匹配元音。在支持字素的语言中执行此操作的方式各不相同,但至少您应该能够将该内容放入NFD并进行匹配 。在Java中,你甚至不能做那么多:字素是你无法企及的。这意味着Java甚至无法处理自己的本机字符集。它为您提供了Unicode,然后使其无法使用它。\X
(?:\p{Grapheme_Base}\p{Grapheme_Extend}]*)
(?:(?=[aeiou])\X)
-
String 类中的便利方法不缓存已编译的正则表达式。事实上,没有编译时模式这样的东西在编译时进行语法检查-编译时应该进行语法检查。这意味着你的程序,除了在编译时完全理解的常量正则表达式之外什么都不使用,如果你在这里或那里忘记了一个小的反斜杠,就会在运行过程中出现异常,因为前面讨论的缺陷是不会做的。即使是Groovy也把这部分做对了。正则表达式是一个太高级的构造,无法通过Java令人不快的事后螺栓固定在一边的模型来处理 - 而且它们对于常规文本处理来说太重要了,不容忽视。对于这些东西来说,Java是一门太低级的语言,它无法提供简单的机制,从中可以自己构建你需要的东西:你不能从这里到达那里。
-
和 类在 Java 中标记。这完全扼杀了使用适当的OO设计来扩展这些类的任何可能性。您无法通过子类化和替换来创建更好的方法版本。哎呀,你甚至不能子类!最终不是解决办法;最后是解决办法。最终是死刑判决,没有上诉。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设计的正常工具也是禁止的,因为它都被死刑判决的最终性锁定了,并且无法修复。
所以 Alireza Noori,如果你觉得 Java 笨拙的正则表达式太笨拙了,无法在 Java 中实现可靠和方便的正则表达式处理,我不能说你。很抱歉,但事实就是如此。
“在下一版本中已修复!”
仅仅因为有些事情永远无法修复,并不意味着没有什么可以修复。它只需要非常小心地完成。以下是我所知道的在当前JDK7或建议的JDK8版本中已经修复的内容:
-
现在支持 Unicode 脚本属性。您可以使用任何等效的形式 、 、 或 。这本质上优于旧的笨重块属性。这意味着你可以做这样的事情,这非常重要。\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}]
-
现在支持命名组通过标准表示法来创建它并反向引用它。这些仍然有助于数字组数。但是,您不能以相同的模式获得多个它们,也不能将它们用于递归。(?<NAME>⋯)
\k<NAME>
-
一个新的 Pattern 编译标志和关联的嵌入式开关 现在将围绕 、 、 和 等内容的所有定义进行交换,以便它们现在符合 Unicode 标准所要求的那些内容的定义。Pattern.UNICODE_CHARACTER_CLASSES
(?U)
\w
\b
\p{alpha}
\p{punct}
-
缺少或错误定义的二进制属性 ,现在将受支持,这些属性对应于类中的方法。这很重要,因为 Unicode 在单纯字母和大小写或字母代码点之间做出了重要而普遍的区分。这些关键属性是 1 级符合 UTS#18 “Unicode Regular Expresions” 绝对需要的 11 个基本属性之一,没有这些属性,您就无法使用 Unicode。\p{IsLowercase}
\p{IsUppercase}
\p{IsAlphabetic}
Character
这些增强功能和修复程序最终非常重要,因此我很高兴,甚至很兴奋。
但是对于工业级的、最先进的正则表达式和/或Unicode工作,我不会使用Java。Java的20年后仍然不完整的Unicode模型中缺少太多了,如果你敢于使用Java提供的字符集,就无法完成真正的工作。而螺栓固定在一边的模型永远不会起作用,这就是Java正则表达式的全部。你必须从第一原则重新开始,就像Groovy所做的那样。
当然,它可能适用于非常有限的应用程序,这些应用程序的客户群很小,仅限于爱荷华州农村的英语单语,没有外部交互,也不需要旧式电报可以发送的字符。但是,对于多少项目来说,这是真的吗?事实证明,即使你认为的更少。
正是出于这个原因,最近一个特定(且显而易见的)数十亿美元取消了一个重要应用程序的国际部署。Java的Unicode支持 - 不仅在正则表达式中,而且在整个过程中 - 被证明太弱,无法在Java中可靠地完成所需的国际化。正因为如此,他们被迫从原计划的全球部署缩减到仅仅是美国的部署。这是积极的狭隘的。不,有Nᴏᴛ Hᴀᴘᴘʏ;你会吗?
Java已经有20年的时间把它做好了,到目前为止,他们显然还没有这样做,所以我不会屏住呼吸。或者投善钱后再投善钱;这里的教训是忽略炒作,而是进行尽职调查,以确保在您投入太多之前所有必要的基础设施支持都在那里。否则,一旦你太深入,无法挽救你的项目,你也可能会陷入没有任何真正选择的困境。
警告 Emptor