我是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.Matches
或Regex.Match
等静态函数时,为什么要创建两个对象?
在更高级的用法中,差异显示出更多.再看方法Groups
,辞典长度Capture
,Index
,Length
,Success
,等等,这些都是非常必要的功能,在我看来应该是可用于Java太.
当然,所有这些功能都可以通过自定义代理(帮助程序)类手动添加.这是我问这个问题的主要原因.我们没有Regex
Perl 的微风,但至少我们可以使用Regex
我认为非常巧妙设计的.Net方法.
tch*_*ist 111
从您编辑的示例中,我现在可以看到您想要的内容.你也对此表示同情.从Ruby或Perl中找到的便利开始,Java的正则表是很长很长的路.他们几乎总是如此; 这是无法修复的,所以我们永远陷入这种混乱 - 至少在Java中.其他JVM语言在这方面做得更好,尤其是Groovy.但它们仍然存在一些固有的缺陷,而且只能走得那么远.
从哪里开始?有String类的所谓的便利方法:matches
,replaceAll
,replaceFirst
,和split
.根据您使用它们的方式,这些在小程序中有时可以正常使用.但是,它们确实存在一些问题,您似乎已经发现了这些问题.以下是这些问题的部分清单,以及可以和不可以做些什么.
不方便的方法非常奇怪地命名为"匹配",但它要求您在两侧填充正则表达式以匹配整个字符串.这种反直觉的感觉与任何以前的语言中使用的任何单词匹配感相反,并且不断地咬人.传递给其他3种不便方法的模式与此不同,因为在其他3种方法中,它们的工作方式与其他地方的普通模式相同; 只是不在matches
.这意味着你不能只是复制你的模式,即使是在同一个darned类中的方法中,为了善意!并且没有find
方便的方法来做世界上每个其他匹配器所做的事情.matches
应该调用该方法FullMatch
,并且应该在String类中添加一个PartialMatch
或find
方法.
没有API允许您传入Pattern.compile
标记以及用于String类的4个与模式相关的便捷方法的字符串.这意味着你很可能依赖像(?i)
和的字符串版本(?x)
,但是对于所有可能的Pattern编译标志都不存在.至少可以说这是非常不方便的.
split
在边缘情况下,该方法不会返回与split
Java借用的语言中的返回相同的结果.这是一个偷偷摸摸的小骗子.如果拆分空字符串,您认为应该在返回列表中返回多少个元素,是吗?Java制造商应该有一个假的返回元素,这意味着你无法区分合法的结果和虚假的结果.这是一个严重的设计缺陷,分裂为a ":"
,你无法分辨""
vs的输入之间的区别":"
.噢,哎!人们不测试这些东西吗?再一次,破碎和根本不可靠的行为是不可修复的:你必须永远不要改变东西,甚至是破碎的东西.用Java来破坏破碎的东西是不行的.破碎永远在这里.
正则表达式的反斜杠表示法与字符串中使用的反斜杠表示法冲突.这使得它超级笨拙,而且容易出错,因为你必须经常向所有东西添加大量的反斜杠,而且很容易忘记一个并且既没有警告也没有成功.简单的模式就像\b\w+\b
印刷过剩的噩梦一样:"\\b\\w+\\b"
.祝你好运.有些人在他们的模式上使用斜线反转功能,以便他们可以将其写为"/b/w+/b"
相反.除了从字符串中读取模式之外,没有办法以所见即所得的文字方式构建模式; 它总是装满了反斜杠.你把它们全部,足够,并在正确的地方得到了吗?如果是这样,那真的很难读.如果不是,你可能还没有得到它们.至少像Groovy这样的JVM语言在这里找到了正确的答案:为人们提供一流的正则表达式让你不要疯狂.这是一个Groovy正则表达式示例的公平集合,显示它可以和应该是多么简单.
这种(?x)
模式存在严重缺陷.它没有采用Java风格的注释,// COMMENT
而是采用shell风格# 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}]
因为正则表达式编译器搞砸了UTF-16.同样,这永远不会被修复或它将改变旧程序.你甚至无法通过使用正常的解决方法来解决这个问题java -encoding UTF-8
,因为愚蠢的东西将字符串存储为令人讨厌的UTF-16,它必然会在字符类中将它们分解,因此使用Java的Unicode-in-source-code麻烦. OOPS!
Java中缺少许多我们在其他语言中依赖的正则表达式.示例中没有命名组,甚至也没有相对编号的组.这使得从较小的模式构建较大的模式从根本上是容易出错的.有一个前端库,允许您拥有简单的命名组,实际上这将最终到达生产JDK7.但即便如此,也没有机制可以使用同一个名称来处理多个组.而且你仍然没有相对编号的缓冲区.我们又回到了Bad Old Days,这是以前解决过的问题.
没有支持换行序列,这是该标准中仅有的两个"强烈推荐"部分之一,这表明\R
可以用于此类.由于其可变长度性质以及Java缺乏对字形的支持,因此难以模拟.
字符类转义不适用于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世纪.
支持的唯一两个Unicode属性是"常规类别"和"块"属性.一般的类别属性只支持类的缩写\p{Sk}
,违背了标准的强烈推荐中,也允许\p{Modifier Symbol}
,\p{Modifier_Symbol}
等你甚至不得到所需要的别名标准说,你应该.这使您的代码更难以理解且无法维护.您最终将获得生产JDK7中Script属性的支持,但这仍然严重缺少标准所说的必须提供的11个基本属性的最小集合,即使是最低级别的Unicode支持也必须提供.
Java提供的一些微不足道的属性是虚假的amis:它们与官方的Unicode propoperty名称具有相同的名称,但它们完全不同.例如,Unicode要求\p{alpha}
与之相同\p{Alphabetic}
,但Java使其成为古老且不再古雅的7位字母,仅超过4个数量级.空白是另一个缺陷,因为你使用伪装成Unicode空格的Java版本,你的UTF-8解析器会因为它们的NO-BREAK SPACE代码点而中断,Unicode规范性地要求将其视为空格,但是Java忽略了这个要求,所以中断你的解析器.
\X
通常提供的方式不支持字素.这使得你需要和想要使用正则表达式完成许多常见任务.不仅扩展的字形集群超出了您的范围,因为Java几乎不支持任何Unicode属性,您甚至无法使用该标准来近似旧的旧字体集群(?:\p{Grapheme_Base}\p{Grapheme_Extend}]*)
.不能使用字形使得即使是最简单的Unicode文本处理也是不可能的.例如,无论Java中的变音符号如何,都无法匹配元音.你在使用字形支持的语言中这样做的方式各不相同,但至少你应该能够把东西扔进NFD并匹配(?:(?=[aeiou])\X)
.在Java中,你不能做那么多:字素是你无法企及的.这意味着Java甚至无法处理自己的原生字符集.它为您提供了Unicode,然后无法使用它.
String类中的便捷方法不会缓存已编译的正则表达式.实际上,没有编译时模式可以在编译时进行语法检查 - 这就是应该进行语法检查的时候.这意味着你的程序只使用在编译时完全理解的常量正则表达式,如果你在这里或那里忘记了一点反斜杠,那么会因为前面讨论的缺陷而忽略了一个例外. .即便是Groovy也能胜任这一部分.正则规则是一个非常高级别的构造,需要由Java不愉快的事后处理模型来处理 - 而且它们对于常规文本处理而言非常重要.Java对于这些东西来说太低级了,它无法提供简单的机制,你自己可以构建你需要的东西:你无法从这里获得.
在String
和Pattern
类被标记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版本中修复的内容:
现在支持Unicode Script属性.您可以使用任何的等价形式\p{Script=Greek}
,\p{sc=Greek}
,\p{IsGreek}
,或\p{Greek}
.这本质上优于旧的笨重块性质.这意味着你可以做一些[\p{Latin}\p{Common}\p{Inherited}]
非常重要的事情.
UTF-16错误有一个解决方法.您现在可以使用\x{?}
符号指定任何Unicode代码点,例如\x{1D402}
.这甚至可以在角色类中使用,最终允许[\x{1D400}-\x{1D419}]
正常工作.你仍然必须加倍反斜杠,它只适用于regexex,而不是一般的字符串,因为它应该.
现在,通过标准符号支持命名组(?<NAME>?)
来创建它并对其\k<NAME>
进行反向引用.这些仍然有助于数字组编号.但是,您不能以相同的模式获取多个,也不能将它们用于递归.
一种新的模式编译标志,Pattern.UNICODE_CHARACTER_CLASSES
以及相关的嵌入式交换机(?U)
,现在将交换的东西像身边所有的定义\w
,\b
,\p{alpha}
,和\p{punct}
,使他们现在符合的Unicode标准所要求的那些东西的定义.
丢失或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年的时间才能做到正确,而且他们到目前为止还没有这样做过,所以我不会屏住呼吸.或者在糟糕的情况下抛出好钱; 这里的教训是忽略炒作,而是适用尽职调查做出非常确保所有必要的基础设施支持,是有之前你投入太多.否则,一旦你太过于拯救你的项目,你也可能会在没有任何实际选择的情况下陷入困境.
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)