本文鏈接 https://unmi.cc/understand-java-regex-backslash/, 來自 隔葉黃鶯 Unmi Blog
Java 語言里的幾大變革,一為 jdk1.4 引入的正則表達式,jdk1.5 引入的泛型。沒有泛型之前有不少人曾想方設法從編譯器入手讓 Java 支持泛型。說到泛型 Perl 無疑是該方面的佼佼者,雖然我們不要求 Java 的正則表式能像 Perl 那樣可以用來寫詩,但至少能有 JavaScript 好用些,可是還不如。JavaScript 里 // 兩斜線一框就是一個模式,分組和后向引用更方便,當然前面那兩家伙是動態的,不太好比。
復雜的用法不說,且說 Java 的正則表達式在匹配點(.) 和斜杠(\),表達式要分別寫作 \\. 和 \\\\,難看些,不好理解。幸好還有些人記住了,匹配點(.) 或 {、[、(、?、$、^ 和 * 這些特殊符號要要前加雙斜框,匹配 \ 時要用四斜杠,這確實能讓你包走天涯的。那么為什么是這樣呢,不是一個斜杠、三個或更多呢,所以知其然還要知其所以然,這樣才能每次心中有數,方能以一變應萬變。
首先,Java 的正則表達式語法說明參見:https://download.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html。
用最簡單的例子來說明問題吧,不創建 Pattern、Matcher 等對象,就看 String 對象的 replaceAll(String regex, String replacement),它第一個參數接收的就是一個正則表達式,我們可以在 IDE 里的調試器中看 "a.b".replaceAll(".","") 能不能得到你期望的結果。
先說為什么像點號(其他的特殊符號還有引號中的 "{、[、(、?、$、^ 和 *")前面要加雙斜杠,注意逗號(,) 不是這一類特殊字符,因為它只會出現在中括號或花括號中。
顯然,如果直接執行
"a.b".replaceAll(".",""); //返回空字符串
得到的值不是你想要的結果,成空字符串了,因為點號 "." 匹配了所有的字符,那要只匹配點號該如何呢,對的,雙斜杠
"a.b".replaceAll("\\.",""); //對的,得到的是 ab
那為什么是雙斜杠呢?這個很簡單,因為點號(.),是個特殊字符,所以它前面需要需要加個斜杠給它轉義,你要真只用一個斜杠來轉義,問題就來了,提示你:
Invalid escape sequence (valid ones are \b \t \n \f \r \" \' \\),也就是 Java 不認 \. 序列,所以還需要前面再加一道杠給其后的斜杠轉義出一個斜杠給點號(.) 用,也就是在 Java 字符串看起來是 “\\.”, 但作為正則表達式來說就是 “\.”,這於其語言的正則表達式是一致的。
也就是說 Java 的正則表達式字符串有兩層次的意義,那就是 Java 字符串轉義出符合正則表達式語法的字符串,“\\.”, 轉義后交給正則表達式的就是 “\.”,這是符合傳統的。因為我們平時字符串轉義后直接用於輸出,所以帶來不少誤解,這里的最終的正則表達式就是 Java 字符串的輸出。
細心的同志一定能看到在調試器里的顯示,看我們寫成的“\\.”, 在調試器里顯示的是 “\\\\.”,說的是如果我們要得到 “\\.”,這樣的輸出那 Java 的字符串就必須寫成 “\\\\.”, 兩個斜杠轉義出一個斜杠。
好的,理解了上面的由來,我們來看看用四個斜杠來匹配一個斜杠的原理。主要原因是斜杠 \ 本身就是用於轉義別的字符的,當然它的架子不是一般的大。因為正則表達式串就是 Java 字符串的輸出,正常思維在正則表達式里匹配斜杠用 “\\”, 那么在 Java 程序里向控制台輸出 “\\”雙斜杠該如何寫呢,對了,就是 “\\\\”,就這么簡單。
再一次從錯誤里找下原因吧,假如我們寫成:
"a\\b".replaceAll("\", "");
報什么錯呢?String literal is not properly closed by a double-quote,因為斜杠把其后的雙引號給轉義了,當然字符串是未結束。再給它加個斜杠又如何呢?
"a\\b".replaceAll("\\", "");
Java 的語法是通過了,但是執行正則表達式不干了,你轉義出來的交給正則表達式的一個斜杠,叫它情何以堪,該去轉義誰呢?所以運行時異常報 An exception occurred: java.util.regex.PatternSyntaxException。
如果寫成三個斜杠呢?
"a\\b".replaceAll("<a>\\\</a>", ""); //與單個斜杠是一樣的異常,掛單的斜杠把雙引號給轉義了
所以這樣推來推去也是該寫成
"a\\b".replaceAll("<a>\\\\</a>", "");
對於正則表達式看到的就是 “\\”,哪種語言的正則表達式要的也是這個,也是第一個斜杠轉義了第二個,第三個轉義了第四個,最終就是 “\\”,正則表達式里轉互相轉義一下就是 “\”了。
我原來理解還只是停留下轉義啊,再轉義的基礎上,隨着寫這篇才更加理解到其中要義的,才發現,原來 Java 的正則表達式和其他語言的正則表達式語言是統一的。只要記住一點,你要想的正則表達式字符串是什么,而正則表達式字符串就是 Java 字符串的的輸出結果,你就知道應該怎么寫了。
最后來看下 Eclipse 調試器里僅匹配單個斜杠時,IDE 里顯示的有多瘋狂:
這讓你體驗到四個斜杠又何其多也。注:全文中的斜框標准意義上應該叫做反斜杠,在此就不作全文替換了。