Java 黑马程序员学习笔记(进阶篇5)
常用 API
1. 正则表达式
(1) 正则表达式(Regex)是一种字符串匹配 / 处理的规则表达式,核心作用是:快速校验字符串格式(如手机号、邮箱)、提取目标内容、替换敏感字符等,避免写大量 if-else 判断,提升字符串处理效率。
(2) 适用场景:表单验证(手机号、身份证)、日志内容提取、字符串批量替换(如敏感词过滤)。
(3) 正则核心语法:
符号 | 含义 | 举例 |
---|---|---|
[ ] | 里面的内容出现一次 | [0-9]、[a-zA-Z0-9] |
( ) | 分组 | a(bc)+ |
^ | 取反(在方括号内时) | [^abc] |
&& | 交集,不能写单个的 & | [a-z&&[m-p]] |
| | 写在方括号外面表示并集 | [a-zA-Z0-9]、x|X |
. | 任意字符(\n 回车符号不匹配) | |
\ | 转义字符 | \d |
\d | 0-9 | \d+ |
\D | 非 0-9 | \D+ |
\s | 空白字符 | [ \t\n\x0B\f\r] |
\S | 非空白字符 | [^\s] |
\w | 单词字符(a-zA-Z_0-9) | [a-zA-Z_0-9] |
\W | 非单词字符 | [^\w] |
注意:Java 字符串中,\
需转义为 \\
(如 \d
要写成 \\d
)。
(4) 量词(匹配多个字符,修饰前一个字符)
量词 | 说明 | 示例(匹配结果) |
---|---|---|
* | 匹配前一个字符 0 次或多次 | ab* → 匹配 "a"、"ab"、"abb" |
+ | 匹配前一个字符 1 次或多次 | ab+ → 匹配 "ab"、"abb"(不匹配 "a") |
? | 匹配前一个字符 0 次或 1 次 | ab? → 匹配 "a"、"ab"(不匹配 "abb") |
{n} | 匹配前一个字符恰好 n 次 | a{3} → 匹配 "aaa" |
{n,} | 匹配前一个字符至少 n 次 | a{2,} → 匹配 "aa"、"aaa" |
{n,m} | 匹配前一个字符 n 到 m 次 | a{2,3} → 匹配 "aa"、"aaa" |
(5) 练习
题目 1:邮箱格式验证正则表达式的应用
请设计一个 Java 程序,使用正则表达式验证邮箱地址格式是否符合以下规则:
① 用户名部分:由 1 个及以上字母、数字或下划线组成
② 包含 @符号
③ 域名主体部分:由 2-6 个字母或数字组成(不能包含下划线)
④ 顶级域名部分:由 1-2 个后缀组成,每个后缀以点号 (.) 开头,且由 2-3 个字母组成(例如.com
、.cn
、.co.uk
等)
⑤ 请使用正则表达式实现上述验证,并测试以下 3 个邮箱地址是否符合规则,输出匹配结果(true/false):
- "3232323@qq.com"
- "zhangsan@itcast.cnn"
- "dlei0009@163.com"
public class RegexExamples {public static void main(String[] args) {// 邮箱格式验证正则表达式String regex3 = "\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2}"; System.out.println("3232323@qq.com".matches(regex3));System.out.println("zhangsan@itcast.cnn".matches(regex3));System.out.println("dlei0009@163.com".matches(regex3));}
}
关键逻辑 1:为什么 [^_] 必须用 [ ] 包裹
[^_]
中的 ^
是 “否定” 符号,但仅在 []
内有效,^
这个符号在正则中有两种完全不同的含义:
- 当
^
放在[]
内部开头时,表示 “否定”,即 “排除后面的字符”。
所以[^_]
表示 “匹配除了下划线_
之外的任意一个字符”。 - 当
^
不在[]
内时,它是 “锚定符”,表示 “匹配字符串的开头位置”。
例如^abc
表示 “匹配以 abc 开头的字符串”。
关键逻辑 2:为什么用 \\. 而不用 .
- 在正则表达式中,
.
是一个特殊元字符,它的含义是 “匹配除换行符之外的任意单个字符”,而不是字面意义上的点号(.)。 - 在你的代码中,需要匹配邮箱地址中的点号(例如
.com
、.cn
中的点),这时候就需要将特殊元字符.
转义为普通字符。转义的方式是在它前面加一个反斜杠\
,即\.
,这样正则引擎就会把它当作字面意义上的点号来处理。
题目 2:使用正则表达式提取文本中的特定格式内容
请设计一个 Java 程序,实现从以下文本中提取所有符合 “Java 后跟 0-2 个数字” 格式的子串(例如 “Java”“Java8”“Java11” 等):
(1) 文本内容:
"Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台"
(2) 具体要求:
定义一个method1
方法,在方法中使用正则表达式 “Java\d {0,2}” 创建Pattern
和Matcher
对象,并调用find()
方法进行至少一次匹配(无需提取结果,仅执行查找操作)。
在main
方法中:
- 调用
method1
方法处理上述文本; - 再次使用相同的正则表达式,通过
while
循环配合find()
和group()
方法,提取并打印所有符合格式的子串。
package demo1;import java.util.regex.Matcher;
import java.util.regex.Pattern;public class test6 {public static void main(String[] args) {String str = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +"因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";method1(str);Pattern p = Pattern.compile("Java\\d{0,2}"); //不太理解Matcher m = p.matcher(str); //不太理解while(m.find()) {String s = m.group();System.out.println(s);}}private static void method1(String str) {Pattern p = Pattern.compile("Java\\d{0,2}"); //不太理解Matcher m = p.matcher(str); //不太理解boolean b = m.find();}
}
关键逻辑 1: Pattern p = Pattern.compile 的作用
(1) Pattern
是 Java 正则的 “模板类”,compile
方法的作用是把上面的正则字符串 “编译” 成一个 Pattern
对象。
可以理解为:
- 正则字符串是 “文字描述”(比如 “找一个叫 Java、后面最多带 2 个数字的内容”);
Pattern
对象是 “实体模板”(根据文字描述制作的一个 “模具”,可以反复用来检查字符串)。
(2) 编译的好处是:如果需要多次使用同一个正则(比如在循环或多个方法中),编译一次后复用 Pattern
对象,能提高效率。
关键逻辑 2:理解 Matcher m = p.matcher(str);
(1) Matcher
是什么?
Matcher
是 Java 正则的 “执行者”,它的作用是:
- 拿着
Pattern
模板(比如 “找 Java+0-2 个数字”); - 去扫描目标字符串
str
(你定义的那段关于 Java 版本的文本); - 找到符合模板的子串,并提供提取这些子串的方法(比如
group()
)。
(2) p.matcher(str)
的含义
p
是我们前面编译好的 Pattern
模板,str
是要检查的目标文本。p.matcher(str)
就相当于 “用模板 p
去适配文本 str
”,返回的 Matcher
对象 m
就是这个 “适配工具”。
关键逻辑 3:先理解 m.find()
的作用
m
是前面创建的 Matcher
对象(匹配器),find()
方法的功能是:
从当前位置开始,在目标文本中查找 “下一个” 符合正则规则的子串。
- 返回
true
:表示找到了一个匹配项,同时内部指针会 “移动到这个匹配项的末尾”(下次调用find()
会从这里继续往后找)。 - 返回
false
:表示从当前位置往后,再也没有符合规则的子串了。
关键逻辑 4:m.group()
的作用
(1) 当 m.find()
返回 true
时(表示找到了一个匹配项),m.group()
用于获取当前找到的那个 “匹配子串”。
(2) 可以理解为:find()
负责 “发现目标”,group()
负责 “取出目标”。
题目 3:有条件的爬取数据
问题描述:
(1) 给定一段关于 Java 版本发展的文本:
"Java 自从 95 年问世以来,经历了很多版本,目前企业中用的最多的是 Java8 和 Java11,因为这两个是长期支持版本,下一个长期支持版本是 Java17,相信在未来不久 Java17 也会逐渐登上历史舞台"
(2) 请编写 Java 程序,从这段文本中提取所有 "Java" 字符串,但要求这些 "Java" 后面必须紧跟着 "8"、"11" 或 "17"(即仅匹配 "Java8"、"Java11"、"Java17" 中的 "Java" 部分),并将提取到的 "Java" 依次打印输出。
package demo1;import java.util.regex.Matcher;
import java.util.regex.Pattern;public class test11 {public static void main(String[] args) {String s ="Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11," +"因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";String regex = "Java(?=8|11|17)"; //不太理解Pattern p = Pattern.compile(regex);Matcher m = p.matcher(s);while(m.find()) {System.out.println(m.group());}}
}
关键逻辑 2:代码里 ?= 的作用
(1) ?=
是正则表达式中正向预查(Positive Lookahead) 的语法标记,核心作用是:
(2) 判断当前位置后面是否紧跟着符合特定规则的内容,但不会将这些内容包含在匹配结果中(仅做条件检查,不消耗字符)。
以你的代码为例:Java(?=8|11|17)
这里的 (?=8|11|17)
表示:
- 检查 “Java” 后面是否紧跟着 “8”“11” 或 “17”;
- 如果符合条件,就匹配 “Java”;
- 但最终的匹配结果里只包含 “Java”,后面的 “8”“11”“17” 不会被包含进来(仅作为匹配条件)。
题目 4:提取不区分大小写的 Java 版本完整字符串
问题描述:
给定一段描述 Java 版本发展的文本:
"Java 自从 95 年问世以来,经历了很多版本,目前企业中用的最多的是 Java8 和 Java11,因为这两个是长期支持版本,下一个长期支持版本是 Java17,相信在未来不久 Java17 也会逐渐登上历史舞台"
请编写 Java 程序,从这段文本中提取所有满足以下条件的完整字符串:
(1) 包含 “Java”(不区分大小写,即 “Java”“java”“JAVA” 等形式都需匹配,本题文本中仅含 “Java”);
(2) “Java” 后面必须紧跟 “8”“11” 或 “17”(即需匹配 “Java8”“Java11”“Java17” 这类完整组合)。
(3) 最终需将提取到的完整字符串依次打印输出。
String regex2 = "((?i)Java)(?:8|11|17)";
关键逻辑:?: 的作用
(1) ?:
是正则表达式中 “非捕获组(Non-capturing Group)” 的语法标记,作用是将多个子表达式 “打包” 成一个整体进行匹配,但不保存该组的匹配结果(即不会占用捕获组的编号,也无法通过 group(n)
方法引用)。
(2) 非捕获组的本质是 “工具性分组”,它的唯一目的是将多个元素(如选项、重复模式等)视为一个整体,方便使用 |
(或)、*
(任意次)、+
(至少一次)等量词修饰。
(3) 例子说明:
假设需要匹配 “Java8”“Java11”“Java17” 这三种字符串:
用非捕获组:Java(?:8|11|17)
- 这里
(?:8|11|17)
将 “8”“11”“17” 打包成一个整体选项,确保 “Java” 后面只能接这三个值。 - 匹配结果是完整的 “Java8”“Java11” 等,但 “8”“11”“17” 不会被单独保存为捕获组。