Scala正则表达式特殊规则详解
Scala正则表达式通过Regex类实现,字符串字面量用r方法转换,需注意反斜杠等特殊字符的转义。支持字符类、量词(*+?{n,m})、分组捕获、边界匹配(^$\b)及特殊字符转义。模式匹配中可结合分组与replaceAllIn实现替换,如手机号脱敏。
字面量规则
在 Scala 里,正则表达式是通过 scala.util.matching.Regex 这个类来体现的。最直接的方式就是用 r 方法把一个字符串字面量转换成正则表达式。举个例子,val pattern = "abc".r 跑完之后,pattern 就是一个正儿八经的正则对象,专门用来匹配包含 abc 的字符串。
不过有个小坑:当正则里要匹配那些在 Scala 字符串里本身就带特殊含义的字符(比如反斜杠 \)时,必须做转义。比如你想匹配一个反斜杠字符,正则里写 "\\",而在 Scala 代码里创建对象就得写成 val backslashPattern = "\\\\".r。原因很简单:Scala 字符串里一个反斜杠需要另一个反斜杠来转义,到了正则层面,反斜杠本身又是特殊字符(用于标记字符类、转义等),所以里外里一共要写四个反斜杠——听着绕,但习惯就好。
字符类规则
字符类用方括号 [] 来定义。比如 [abc] 能匹配 a、b 或 c 这三个字符中的任意一个。在 Scala 里写成 val charClassPattern = "[abc]".r 就行。
如果字符范围是连续的,可以用范围表示法,比如 [a-z] 匹配所有小写字母,对应代码 val lowerCasePattern = "[a-z]".r。
如果你想匹配“除了某几个字符以外的任意字符”,就在方括号内第一个位置放一个 ^。例如 [^abc] 会匹配任何不是 a、b、c 的字符。Scala 代码为 val notABCPattern = "[^abc]".r。
量词规则
量词控制的是“前面那个元素出现多少次”。这块是正则里最常用的部分,我们来逐一梳理。
*表示前面的元素出现零次或多次。比如a*可以匹配空字符串、a、aa、aaa……说白了就是“有也行,没有也行,越多越好”。Scala 里val starPattern = "a*".r。+表示前面元素出现一次或多次。比如a+匹配a、aa……但不匹配空串。Scala 代码val plusPattern = "a+".r。表示前面元素出现零次或一次。比如a只能匹配空串或者单个a。Scala 写成val questionMarkPattern = "a".r。- 用
{n}可以指定精确的出现次数。例如a{3}只匹配连续三个a,即“aaa”。Scala 代码val exactCountPattern = "a{3}".r。 {n,}表示至少 n 次。比如a{2,}匹配aa、aaa……Scala 代码val atLeastCountPattern = "a{2,}".r。{n,m}表示至少 n 次,最多 m 次。例如a{1,3}匹配a、aa、aaa,但不匹配四个a。Scala 代码val rangeCountPattern = "a{1,3}".r。
分组规则
分组用圆括号 () 实现。它的作用是两方面的:一是能把一组字符当成一个整体来应用量词,二是能在匹配后提取出子串(即“捕获组”)。
比如 (ab)+ 可以匹配 ab、abab、ababab……注意,这里 + 作用在 (ab) 这个整体上。Scala 里写 val groupPattern = "(ab)+".r。
当拿到匹配结果后,你可以通过 match 语句或者 findFirstMatchIn 等方法获取每个分组捕获到的具体内容。这个能力在实际项目中非常有用,比如提取电话号码、身份证号里的特定部分。
边界匹配规则
边界匹配不匹配具体字符,而是匹配位置。主要有三个。
^匹配字符串开头。比如^abc只匹配以abc开头的字符串。Scala 代码val startPattern = "^abc".r。$匹配字符串结尾。比如abc$只匹配以abc结尾的字符串。Scala 代码val endPattern = "abc$".r。\b匹配单词边界。单词边界指的是单词字符(字母、数字、下划线)和非单词字符之间的位置。例如\babc\b能匹配独立的abc单词,但不会匹配abcd里的abc。在 Scala 里要小心转义:val wordBoundaryPattern = "\\babc\\b".r。
特殊字符转义规则
正则表达式里很多字符都有特殊含义,比如 ^(在字符类外表示开头)、$(结尾)、*(量词)等等。如果你想匹配这些字符本身,就需要用反斜杠进行转义。比如匹配一个星号,正则里要写成 \*,然后在 Scala 代码中创建对象时写成 val asteriskPattern = "\\*".r。——记住,每一次转义都意味着在字符串层面要多加一层反斜杠。
模式匹配中的特殊规则
Scala 的模式匹配和正则结合得相当紧密,你可以在 match 语句中直接使用正则表达式来匹配字符串,非常方便。下面这个例子能让你直观感受到分组和替换的威力:
package test37
// 任务:把字符串中的手机号打码:类似:138****5678
object test2 {
def main(args: Array[String]): Unit = {
var str= "张先生,手机号:13812345678 你买的 xxx 到了"
// 开始你的代码
// ()是分组,不会影响正则表达式的查询结果,但是会把分组找到的内容单独保存在group中
val reg = "(1[3-9]\\d)(\\d{4})(\\d{4})".r
// reg.replaceAllIn的功能:在目标字符串str中,用正则表达式reg去查找,找到之后,用箭头函数的返回值来替换
// m就是正则表达式找到的内容
// 如果正则表达式中有(),则可以通过m.group来找到对应的分组
val newStr = reg.replaceAllIn(str,(m)=>{
println(s"第一组:${m.group(1)}")
println(s"第二组:${m.group(2)}")
println(s"第三组:${m.group(3)}")
println("-"*48)
m.group(1)+"****"+m.group(3)
})
println(newStr)
// 输出:张先生,手机号:138****5678 你买的 xxx 到了
}
}
这个例子里,我们把手机号分成了三段:前三位(运营商号段)、中间四位(隐藏)、后四位。然后通过 replaceAllIn 和分组提取,只保留第一组和第三组,中间用 **** 替代,轻松实现脱敏。分组判断、替换逻辑都在一行 lambda 里完成,简洁又实用。
