现在的位置: 首页 > 综合 > 正文

java中\的理解

2013年05月28日 ⁄ 综合 ⁄ 共 4050字 ⁄ 字号 评论关闭

Java 语言里的几大变革,一为 jdk1.4 引入的正则表达式,jdk1.5 引入的泛型。没有泛型之前有不少人曾想方设法从编译器入手让 Java 支持泛型。说到泛型  Perl 无疑是该方面的佼佼者,虽然我们不要求 Java 的正则表式能像 Perl 那样可以用来写诗,但至少能有 JavaScript 好用些,可是还不如。JavaScript 里 // 两斜线一框就是一个模式,分组和后向引用更方便,当然前面那两家伙是动态的,不太好比。

复杂的用法不说,且说 Java 的正则表达式在匹配点(.)  和斜杠(\),表达式要分别写作 \\. 和 \\\\, 难看些,不好理解。幸好还有些人记住了,匹配点(.) 或  {、[、(、?、$、^ 和 * 这些特殊符号要要前加双斜框,匹配 \ 时要用四斜杠,这确实能让你包走天涯的。那么为什么是这样呢,不是一个斜杠、三个或更多呢,所以知其然还要知其所以然,这样才能每次心中有数,方能以一变 应万变。

首先,Java 的正则表达式语法说明参见:http://download.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html

用 最简单的例子来说明问题吧,不创建 Pattern、Matcher 等对象,就看 String 对象的 replaceAll(String regex, String replacement),它第一个参数接收的就是一个正则表达式,我们可以在 IDE 里的调试器中看 "a.b".replaceAll(".","") 能不能得到你期望的结果。

先说为什么像点号(其他的特殊符号还有引号中的 "{、[、(、?、$、^ 和 *")前面要加双斜杠,注意逗号(,) 不是这一类特殊字符,因为它只会出现在中括号或花括号中。

显然,如果直接执行

1
"a.b".replaceAll(".","");  //返回空字符串

得到的值不是你想要的结果,成空字符串了,因为点号 "." 匹配了所有的字符,那要只匹配点号该如何呢,对的,双斜杠

1
"a.b".replaceAll("\\.","");  //对的,得到的是
ab

那为什么是双斜杠呢?这个很简单,因为点号(.),是个特殊字符,所以它前面需要需要加个斜杠给它转义,你要真只用一个斜杠来转义,问题就来了,提示你:

java regex slash

Invalid escape sequence (valid ones are \b \t \n \f \r \" \' \\),也就是 Java 不认 \. 序列,所以还需要前面再加一道杠给其后的斜杠转义出一个斜杠给点号(.) 用,也就是在 Java 字符串看起来是 “\\.”, 但作为正则表达式来说就是 “\.”,这于其语言的正则表达式是一致的。

也 就是说 Java 的正则表达式字符串有两层次的意义,那就是 Java 字符串转义出符合正则表达式语法的字符串,“\\.”, 转义后交给正则表达式的就是 “\.”,这是符合传统的。因为我们平时字符串转义后直接用于输出,所以带来不少误解,这里的最终的正则表达式就是 Java 字符串的输出。

java regex slash 1

细心的同志一定能看到在调试器里的显示,看我们写成的“\\.”, 在调试器里显示的是 “\\\\.”,说的是如果我们要得到 “\\.”,这样的输出那 Java 的字符串就必须写成 “\\\\.”, 两个斜杠转义出一个斜杠。

好 的,理解了上面的由来,我们来看看用四个斜杠来匹配一个斜杠的原理。主要原因是斜杠 \ 本身就是用于转义别的字符的,当然它的架子不是一般的大。因为正则表达式串就是 Java 字符串的输出,正常思维在正则表达式里匹配斜杠用 “\\”, 那么在 Java 程序里向控制台输出 “\\”双斜杠该如何写呢,对了,就是 “\\\\”,就这么简单。

再一次从错误里找下原因吧,假如我们写成:

1
"a\\b".replaceAll("\", "");

报什么错呢?String literal is not properly closed by a double-quote,因为斜杠把其后的双引号给转义了,当然字符串是未结束。再给它加个斜杠又如何呢?

1
"a\\b".replaceAll("\\""");

Java 的语法是通过了,但是执行正则表达式不干了,你转义出来的交给正则表达式的一个斜杠,叫它情何以堪,该去转义谁呢?所以运行时异常报 An exception occurred: java.util.regex.PatternSyntaxException

如果写成三个斜杠呢?

1
"a\\b".replaceAll("<a>\\\</a>"""); //与单个斜杠是一样的异常,挂单的斜杠把双引号给转义了

所以这样推来推去也是该写成

1
"a\\b".replaceAll("<a>\\\\</a>""");

对于正则表达式看到的就是 “\\”,哪种语言的正则表达式要的也是这个,也是第一个斜杠转义了第二个,第三个转义了第四个,最终就是 “\\”,正则表达式里转互相转义一下就是 “\”了。

我原来理解还只是停留下转义啊,再转义的基础上,随着写这篇才更加理解到其中要义的,才发现,原来 Java 的正则表达式和其他语言的正则表达式语言是统一的。只要记住一点,你要想的正则表达式字符串是什么,而正则表达式字符串就是 Java 字符串的的输出结果,你就知道应该怎么写了

最后来看下 Eclipse 调试器里仅匹配单个斜杠时,IDE 里显示的有多疯狂:

java_regex_slash_3.jpg

这让你体验到四个斜杠又何其多也。注:全文中的斜框标准意义上应该叫做反斜杠,在此就不作全文替换了。

在Java正则表达式中,反斜杠(backslash)(即”/”)需要表示成”////”,即需要用四个反斜杠表示,在java编程思想上看到这个规则感到很奇怪,书上没说清楚,在网上查了一些资料,总算有些眉目。在stackoverflow上这篇帖子中有这么一段话:

Yes, there is a general guideline about escaping: Escape sequences in your Java source get replaced by the Java compiler (or some preprocessor eventually). The compiler will complain about any escape sequences it does not know, e.g. \s.
When you write a String literal for a RegEx pattern, the compiler will process this literal as usual and replace all escape sequences with the according character. Then, when the program is executed, the Pattern class compiles the input String, that is, it
will evaluate escape sequences another time. The Pattern class knows \s as
a character class and will therefore be able to compile a pattern containing this class. However, you need to escape \s from
the Java compiler which does not know this escape sequence. To do so, you escape the backslash resulting in \\s.

In short, you always need to escape character classes for RegEx patterns twice. If you want to match a backslash, the correct pattern is \\\\ because
the Java compiler will make it \\ which
the Pattern compiler will recognize as the escaped backslash character.

即:

Java中关于转义有一个通用准则:你的Java代码中的转义序列最终都会由Java 编译器(或其他预处理器)进行替换。编译器对于它不知道的任何转义序列(如\s)都会报错。当你为RegEx pattern写了一个Sting字面值时,编译器会把转义序列替换为相应的字

符,然后,当程序执行时,Pattern类会编译(compile)这段字符串,亦即Pattern类会再次执行一次转义。Pattern类知道\s是一个字符类(character class),所以可以编译包含该类的模式(pattern)。然而,你需要为Java compiler把\s转义一次,因为Java compiler并不知道这个转义序列(\s).这样,对”\s”中的”\”执行一次转义就得到了最后的”\\s”.

简言之,你需要为正则表达式转义两次,如果你要匹配一个反斜杠(“\”),那么正确的模式为”\\\\”.因为Java Compiler会把它(”\\\\”)转义为”\\”,这样,Pattern compiler才会识别为转义的”\”.

用我的话来说,就是Java 字符串和正则表达式都有转义,都用”\\”表示”\”。从一个字符串字面值到正则表达式需要经过两次转义,第一次转义由Java String执行,把字符串中的”\\”表示为”\”,然后,Pattern类又会执行一次转义,同样是把”\\”转义为”\”,

这时,才是你要匹配的东西。所以如果你要匹配反斜杠(“\”),那么你就得经过两次反转义(Pattern和String),经过第一次反转义后,”\”变成了”\\”,经过第二次反转义,”\\”变成了”\\\\”.

对于   \\*    JVM转义为\*   ,正则转义为*,所以这只是表示*字符,而不是表示 零个或多个字符(如果直接\*,则JVM转义为*,在正则中表示另个或多个了)
对于  \\d    JVM转义为\d ,正则认识\d就不转义了吧(如果直接\d,JVM不认识会报错)

抱歉!评论已关闭.