08.枚举类&注解&正则表达式
枚举类的使用
1.枚举类的说明
1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
2.当需要定义一组常量时,强烈建议使用枚举类
3.如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
enum 枚举类类名 {
对象1,对象2,对象3;
}
4.特点
-
1.枚举类里的构造器默认被private修饰,不能使用除了private以外的其他权限修饰符,目的是为了避免外部创建对象
-
2.枚举类里构造器允许重载
-
3.对象只能在枚举类的内部创建,而且创建的对象默认被 public static final 修饰(所以不能被继承,不能被修改)
-
4.创建枚举对象的,不是直接调用构造器new对象,而是只写枚举对象名()
如果调用的是无参的枚举构造器,() 可以省略;如果调用的是有参的构造器,()中传入参数
-
5.创建的枚举对象通常会使用全大写,表示是一个常量
-
6.创建枚举对象的代码必须要写在第一行
-
7.所有定义的枚举类都自动继承自 java.lang.Enum 类,所以枚举不能再继承其他类
-
8.枚举可以实现接口
5.java.lang.Enum 自带两个属性:name ordinal ,所有每个枚举都自带这两个属性
2.如何自定义枚举类
1.使用java原生代码实现
//自定义枚举类
class Season{
//1.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//2.私化类的构造器,并给对象属性赋值
private Season(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//3.提供当前枚举类的多个对象:public static final的
public static final Season SPRING = new Season("春天","春暖花开");
public static final Season SUMMER = new Season("夏天","夏日炎炎");
public static final Season AUTUMN = new Season("秋天","秋高气爽");
public static final Season WINTER = new Season("冬天","冰天雪地");
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//4.其他诉求1:提供toString()
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
2.jdk 5.0 新增使用enum定义枚举类
//使用enum关键字枚举类
enum Season1 {
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"),
SUMMER("夏天","夏日炎炎"),
AUTUMN("秋天","秋高气爽"),
WINTER("冬天","冰天雪地");
//2.声明Season对象的属性:private final修饰
private final String seasonName;
private final String seasonDesc;
//3.私化类的构造器,并给对象属性赋值
private Season1(String seasonName,String seasonDesc){
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//4.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
3.枚举常见方法
enum 继承于 java.lang.Enum 类,Enum 类中重写了方法
调用方法: 类名.对象名.方法名
方法名 | 作用 |
---|---|
toString | 返回的是常量名(对象名),可以重写 |
name | 返回的是常量名(对象名),不推荐使用,建议使用toString |
values | 返回该枚举类的所有的常量对象,返回类型是当前枚举的数组类型。 |
valueOf | 根据常量名,返回一个枚举对象。 |
ordinal | 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。 |
Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());
// System.out.println(Season1.class.getSuperclass());
System.out.println("****************");
//values():返回所的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);
}
System.out.println("****************");
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);
}
//valueOf(String objName):返回枚举类中对象名是objName的对象。
Season1 winter = Season1.valueOf("WINTER");
//如果没objName的枚举类对象,则抛异常:IllegalArgumentException
// Season1 winter = Season1.valueOf("WINTER1");
System.out.println(winter);
4.枚举类中对象分别实现接口
interface Info{
void show();
}
//使用enum关键字枚举类
enum Season1 implements Info{
//1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
SPRING("春天","春暖花开"){
@Override
public void show() {
System.out.println("春天在哪里?");
}
},
SUMMER("夏天","夏日炎炎"){
@Override
public void show() {
System.out.println("宁夏");
}
},
AUTUMN("秋天","秋高气爽"){
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("冬天","冰天雪地"){
@Override
public void show() {
System.out.println("大约在冬季");
}
};
}
5.通过javap反编译理解枚举--单例模式最简单的实现方式
public enum Enumerate {
INSTANCE;
}
Compiled from "Enumerate.java"
public final class Enumerate extends java.lang.Enum<Enumerate> {
public static final Enumerate INSTANCE;
private static final Enumerate[] $VALUES;
public static Enumerate[] values();
public static Enumerate valueOf(java.lang.String);
private Enumerate();
static {};
}
上面是 javap 反编译 Enumerate.class 文件,得到 JVM 实际处理我们定义的 Enumerate「枚举」代码,然后我们一起看一下, JVM 对我们定义的「枚举」做了哪些处理,通过这些处理可以看一下「枚举」类型应该具有那些特性。
-
让我们定义的 Enumerate 继承 java.lang.Enum 类, java.lang.Enum 类接收 Enumerate 泛型。使用了 final 修饰了 Enumerate 类。
-
通过这个定义,我们知道「枚举」类型的本质也是一个类。「枚举」类型不允许继承其他类,因为 Java 规定类是单继承,而「 枚举」类型已经继承了 Enum 类。「枚举」类型不能被其他类继承,因为「 枚举」类型是用 final 关键字修饰的。
我们在 Java 代码中定义的 INSTANCE 「枚举」,被定义成了 Enumerate 类的实例对象,并且这个实例对象是用 public static final 修饰的。 -
我们定义的「枚举」常量是当前「枚举」类型的实例对象,所以他可以调用当前「 枚举」类型中的方法和变量,并且这个实例对象是静态不可变的。
-
定义了静态、私有的 $VALUES 数组,用来存储我们定义的「枚举」。
-
定义了静态的 values() 方法,返回当前「枚举」类型中所有的「枚举」常量。
-
定义了静态的 valueOf 方法,返回指定「枚举」类型的「枚举」常量。
-
定义了私有的构造方法。
[枚举] 类型不能被实例化。
-
定义了 static 静态代码块,用于初始化「枚举」常量和「枚举」常量数组。
我们通过反编译发现我们定义的「枚举」类型继承了 Enum 类,那我们一起来看一下 Enum 类的源码(JDK 1.8)
//Enum类所有Java语言枚举类型的公共基类
public abstract class Enum<E extends java.lang.Enum<E>>
implements Comparable<E>, Serializable
//「枚举」常量的名字
private final String name
//「枚举」常量声明时的顺序
private final int ordinal;
// 获取「枚举」常量 名字
public final String name() {
return name;
}
// 获取「枚举」常量 顺序
public final int ordinal() {
return ordinal;
}
// 构造方法,给「枚举」常量名字和顺序初始化
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
// 直接返回「枚举」常量名称,获取常量名称推荐使用这个方法,而不是使用 name 方法获取「枚举」常量名称
public String toString() {
return name;
}
// 直接比较内存地址
public final boolean equals(Object other) {
return this == other;
}
public final int hashCode() {
return super.hashCode();
}
// 不允许克隆,直接抛出异常
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
// 用来比较 「枚举」常量 的顺序
public final int compareTo(E o) {
java.lang.Enum other = (java.lang.Enum) o;
java.lang.Enum self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
// 此方法返回对应于此枚举常量的枚举类型的Class对象
//compareTo方法中调用了此方法,原因:详见帖子
//https://blog.csdn.net/anlian523/article/details/102531371
public final Class<E> getDeclaringClass() {
Class clazz = getClass();
Class zuper = clazz.getSuperclass();
return (zuper == java.lang.Enum.class) ? clazz : zuper;
}
// 根据「枚举」类型和「枚举」常量名称返回对应的「枚举」常量
public static <T extends java.lang.Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
protected final void finalize() {
}
// 不允许反序列化,直接抛出异常
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
}
「枚举」是单例模式最简单的实现方式。
「枚举」类型的构造函数被编译器定义为 private ,通过 Enum 类源码可以看到,clone 和反序列化直接抛出异常,保证了每个实例对象的唯一性。对象实例都是由 JVM 来负责创建,保证了线程的安全。
下面的代码便是使用「枚举」实现了一个单例,相比于用懒汉、饿汉、双重检测、静态内部类等方式实现单例,简直简单太多了,感觉「枚举」就是 JVM 提供给我们用来实现单例模式的一个语法糖。
public enum Enumerate {
INSTANCE;
}
6.实战中的学习
注解的使用
1.注解的理解
① jdk 5.0 新增的功能
② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,
程序员可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。
③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android
中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
框架 = 注解 + 反射机制 + 设计模式
2.注解的使用示例
示例一:生成文档相关的注解
@author 标明开发该类模块的作者,多个作者之间使用,分割
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写
@param @return 和 @exception 这三个标记都是只用于方法的。
@param的格式要求:@param 形参名 形参类型 形参说明
@return 的格式要求:@return 返回值类型 返回值说明
@exception的格式要求:@exception 异常类型 异常说明
@param和@exception可以并列多个
示例二:在编译时进行格式检查(JDK内置的3个基本注解)
@Override: 限定重写父类方法, 该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的择
@SuppressWarnings: 抑制编译器警告
示例三:跟踪代码依赖性,实现替代配置文件功能
Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署
spring框架中关于“事务”的管理
3.如何自定义注解
1.规则
- 定义新的 Annotation 类型使用 @interface 关键字
- 自定义注解自动继承了java.lang.annotation.Annotation接口
- Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、或以上所有类型的数组。
- 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值可使用default 关键字
- 如果只有一个参数成员,建议使用参数名为value
- 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
- 没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据 Annotation
- 注意:自定义注解必须配上注解的信息处理流程才有意义。
2.示例
参照@SuppressWarnings定义
① 注解声明为:@interface
② 内部定义成员,通常使用value表示
③ 可以指定成员的默认值,使用default定义
④ 如果自定义注解没成员,表明是一个标识作用
说明:
如果注解有成员,在使用注解时,需要指明成员的值
自定义注解必须配上注解的信息处理流程(使用反射)才意义
自定义注解通过都会指明两个元注解:Retention、Target
@Retention 运行时注解还是编译时注解
@Target注解作用范围
代码举例:
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}
4.元注解
JDK 的元 Annotation 用于修饰其他 Annotation 定义:即对现有的注解进行解释说明的注解
JDK5.0提供了4个标准的meta-annotation类型:@Retention @Target @Documented @Inherited
@Retention
@Target
@Documented
@Inherited
5.如何获取注解信息
通过反射来进行获取、调用 前提:要求此注解的元注解Retention中声明的生命周期状态为:RUNTIME(才会被加载到jvm中)
6.JDK8中注解的新特性:可重复注解、类型注解
1.可重复注解(元注解@Repeatable)
代码示例:
需求:在同一个类上使用2次相同的注解,每次value不同
① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。(不同则编译时报错)
@Inherited一个有,则另一个也要有,否则编译不报错,运行时会报错
//jdk 8之前的写法:
//@MyAnnotations({@MyAnnotation(value="hi"),@MyAnnotation(value="hello")})只能使用数组的方式实现
@MyAnnotation(value="hi")
@MyAnnotation(value="hello")
class Person{
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotations {
MyAnnotation[] value();
}
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotation {
String value() default "hello";
}
2.类型注解(@Target value的补充)
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型变量T的声明 然后可以通过反射对其进行其他额外操作)
ElementType.TYPE_USE 表示该注解能写在使用类型
的任何语句中。(只要是类型,前面就都可以加此注解)
代码示例:
class Generic<@MyAnnotation T>{
public void show() throws @MyAnnotation RuntimeException{ //异常前
ArrayList<@MyAnnotation String> list = new ArrayList<>();//泛型前
int num = (@MyAnnotation int) 10L;//强转类型前
}
}
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}
正则表达式
正则表达式是一组由字母和符号组成的特殊文本,它可以用来从文本中找出满足你想要的格式的句子。
一个正则表达式是一种从左到右匹配主体字符串的模式。
“Regular expression”这个词比较拗口,我们常使用缩写的术语“regex”或“regexp”。
正则表达式可以从一个基础字符串中根据一定的匹配模式替换文本中的字符串、验证表单、提取字符串等等。
1. 基本匹配
正则表达式其实就是在执行搜索时的格式,它由一些字母和数字组合而成。 例如:一个正则表达式 the
,它表示一个规则:由字母t
开始,接着是h
,再接着是e
。
"the" => The fat cat sat on the mat.
正则表达式123
匹配字符串123
。它逐个字符的与输入的正则表达式做比较。
正则表达式是大小写敏感的,所以The
不会匹配the
。
"The" => The fat cat sat on the mat.
2. 元字符
正则表达式主要依赖于元字符。 元字符不代表他们本身的字面意思,他们都有特殊的含义。一些元字符写在方括号中的时候有一些特殊的意思。以下是一些元字符的介绍:
元字符 | 描述 |
---|---|
. | 句号匹配任意单个字符除了换行符。 |
[ ] | 字符种类。匹配方括号内的任意字符。 |
[^ ] | 否定的字符种类。匹配除了方括号里的任意字符 |
* | 匹配>=0个重复的在*号之前的字符。 |
+ | 匹配>=1个重复的+号前的字符。 |
? | 标记?之前的字符为可选.(? 也可将贪婪匹配转化为惰性匹配) |
匹配num个大括号之前的字符或字符集 (n <= num <= m). | |
(xyz) | 字符集,匹配与 xyz 完全相等的字符串. |
| | 或运算符,匹配符号前或后的字符. 一般用在() 内 |
\ | 转义字符,用于匹配一些保留的字符 `[ ] ( ) { } . * + ? ^ $ \ |
^ | 从开始行开始匹配. eg: ^a 以a开头 |
$ | 从末端开始匹配. eg: z$ 以z结尾 |
2.1 点运算符 .
.
是元字符中最简单的例子。 .
匹配任意单个字符,但不匹配换行符。 例如,表达式.ar
匹配一个任意字符后面跟着是a
和r
的字符串。
".ar" => The car parked in the garage.
2.2 字符集[]
字符集也叫做字符类。 方括号用来指定一个字符集。 在方括号中使用连字符来指定字符集的范围。 在方括号中的字符集不关心顺序。 例如,表达式[Tt]he
匹配 the
和 The
。
"[Tt]he" => The car parked in the garage.
方括号的句号就表示句号。 表达式 ar[.]
匹配 ar.
字符串
"ar[.]" => A garage is a good place to park a car.
2.2.1 否定字符集
一般来说 ^
表示一个字符串的开头,但它用在一个方括号的开头的时候,它表示这个字符集是否定的。 例如,表达式[^c]ar
匹配一个后面跟着ar
的除了c
的任意字符。
"[^c]ar" => The car parked in the garage.
2.3 重复次数
后面跟着元字符 +
,*
or ?
的,用来指定匹配子模式的次数。 这些元字符在不同的情况下有着不同的意思。
2.3.1 *
号
*
号匹配 在*
之前的字符出现大于等于0
次。 例如,表达式 a*
匹配0或更多个以a开头的字符。表达式[a-z]*
匹配一个行中所有以小写字母开头的字符串。
"[a-z]*" => The car parked in the garage #21.
*
字符和.
字符搭配可以匹配所有的字符.*
。 *
和表示匹配空格的符号\s
连起来用,如表达式\s*cat\s*
匹配0或更多个空格开头和0或更多个空格结尾的cat字符串。
"\s*cat\s*" => The fat cat sat on the concatenation.
2.3.2 +
号
+
号匹配+
号之前的字符出现 >=1 次。 例如表达式c.+t
匹配以首字母c
开头以t
结尾,中间跟着至少一个字符的字符串。
"c.+t" => The fat cat sat on the mat.
2.3.3 ?
号
在正则表达式中元字符 ?
标记在符号前面的字符为可选,即出现 0 或 1 次。 例如,表达式 [T]?he
匹配字符串 he
和 The
。
(?
也可将贪婪匹配转化为惰性匹配)
"[T]he" => The car is parked in the garage.
"[T]?he" => The car is parked in the garage.
2.4 {}
号
在正则表达式中 {}
是一个量词,常用来限定一个或一组字符可以重复出现的次数。 例如, 表达式 [0-9]{2,3}
匹配最少 2 位最多 3 位 0~9 的数字。
"[0-9]{2,3}" => The number was 9.9997 but we rounded it off to 10.0.
我们可以省略第二个参数。 例如,[0-9]{2,}
匹配至少两位 0~9 的数字。
"[0-9]{2,}" => The number was 9.9997 but we rounded it off to 10.0.
如果逗号也省略掉则表示重复固定的次数。 例如,[0-9]{3}
匹配3位数字
"[0-9]{3}" => The number was 9.9997 but we rounded it off to 10.0.
2.5 (...)
特征标群
特征标群是一组写在 (...)
中的子模式。(...)
中包含的内容将会被看成一个整体,和数学中小括号( )的作用相同。
例如, 表达式 (ab)*
匹配连续出现 0 或更多个 ab
。如果没有使用 (...)
,那么表达式 ab*
将匹配连续出现 0 或更多个 b
。
再比如之前说的 {}
是用来表示前面一个字符出现指定次数。但如果在 {}
前加上特征标群 (...)
则表示整个标群内的字符重复 N 次。
我们还可以在 ()
中用或字符 |
表示或。例如,(c|g|p)ar
匹配 car
或 gar
或 par
.
"(c|g|p)ar" => The car is parked in the garage.
非捕获组(?:表达式)
不捕获数据,还能使用分组的功能
2.6 |
或运算符
或运算符就表示或,用作判断条件。
例如 (T|t)he|car
匹配 (T|t)he
或 car
。
"(T|t)he|car" => The car is parked in the garage.
2.7 转码特殊字符
反斜线 \
在表达式中用于转码紧跟其后的字符。用于指定 { } [ ] / \ + * . $ ^ | ?
这些特殊字符。如果想要匹配这些特殊字符则要在其前面加上反斜线 \
。
例如 .
是用来匹配除换行符外的所有字符的。如果想要匹配句子中的 .
则要写成 \.
以下这个例子 \.?
是选择性匹配.
"(f|c|m)at\.?" => The fat cat sat on the mat.
2.8 锚点
在正则表达式中,想要匹配指定开头或结尾的字符串就要使用到锚点。^
指定开头,$
指定结尾。
2.8.1 ^
号
^
用来检查匹配的字符串是否在所匹配字符串的开头。
例如,在 abc
中使用表达式 ^a
会得到结果 a
。但如果使用 ^b
将匹配不到任何结果。因为在字符串 abc
中并不是以 b
开头。
例如,^(T|t)he
匹配以 The
或 the
开头的字符串。
"(T|t)he" => The car is parked in the garage.
"^(T|t)he" => The car is parked in the garage.
2.8.2 $
号
同理于 ^
号,$
号用来匹配字符是否是最后一个。
例如,(at\.)$
匹配以 at.
结尾的字符串。
"(at\.)" => The fat cat. sat. on the mat.
"(at\.)$" => The fat cat. sat. on the mat.
3. 简写字符集
正则表达式提供一些常用的字符集简写。如下:
简写 | 描述 |
---|---|
. | 除换行符外的所有字符 |
\w | 匹配所有字母数字,等同于 [a-zA-Z0-9_] |
\W | 匹配所有非字母数字,即符号,等同于: [^\w] |
\d | 匹配数字: [0-9] , d 即digit 数字的意思 |
\D | 匹配非数字: [^\d] |
\s | 匹配所有空格字符,等同于: [\t\n\f\r\p{Z}] |
\S | 匹配所有非空格字符: [^\s] |
\f | 匹配一个换页符 |
\n | 匹配一个换行符 |
\r | 匹配一个回车符 |
\t | 匹配一个制表符 |
\v | 匹配一个垂直制表符 |
\p | 匹配 CR/LF(等同于 \r\n ),用来匹配 DOS 行终止符 |
\b | 匹配的是单词的边界 |
4.分组的回溯引用
正则表达式还提供了一种引用之前匹配分组的机制,有些时候,我们或许会寻找到一个子匹配,该匹配接下来会再次出现。
例如,要匹配一段 HTML 代码,比如:0123<font>提示</font>abcd
,可能会编写出这样一段正则表达式:
/<\w+>.*?</\w+>/
确实可以匹配到两个<>
标签,但可能匹配结果是:<font>提示</bar>
在这里font
和 bar
明显不是一对正确的标签,但是我们编写的正则表达式还是将它们给匹配了,所以这个结果是错误的。
我们想让后面分组的正则也匹配font
,但是现在所有形式的都会匹配。
那如果想让后面分组的正则和第一个分组的正则匹配同样的数据该如何做呢?
可以使用分组的回溯引用,使用\N
可以引用编号为N
的分组,因此上述例子的代码我们可以改为:
/<\w+>.*?</\1>/
\1
表示的就是第一个分组,在这里第一个分组匹配的是 font
所以\1
就代表font
。
还可以使用
\2
\3
...表示2 3 ...个分组
5. 零宽度断言(前后预查)
很多人也称先行断言和后行断言为环视,也可叫做预搜索
先行断言和后行断言只有一个区别,即先行断言从左往右看,后行断言从右往左看。
先行断言和后行断言总共有四种:
- 正向先行断言
- 反向先行断言
- 正向后行断言
- 反向后行断言
先行断言和后发断言(合称 lookaround)都属于非捕获组(用于匹配模式,但不包括在匹配列表中)。-- 不捕获断言中的内容
零宽度断言如下:
符号 | 描述 |
---|---|
?= | 正先行断言-存在 |
?! | 负先行断言-排除 |
?<= | 正后发断言-存在 |
?<! | 负后发断言-排除 |
1. ?=...
正先行断言
正向先行断言:(?=表达式)
,指在某个位置向右看,表示所在位置右侧必须能匹配表达式
例如:
我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你
如果要取出喜欢两个字,要求这个喜欢后面有你,这个时候就要这么写:喜欢(?=你)
,这就是正向先行断言。
返回结果只包含满足匹配条件的第一部分表达式。(不返回断言中的内容)
2. ?!...
负先行断言
反向先行断言(?!表达式)
的作用是保证右边不能出现某字符。
例如: 我喜欢你 我喜欢 我喜欢我 喜欢 喜欢你
如果要取出喜欢两个字,要求这个喜欢后面没有你,这个时候就要这么写:喜欢(?!你)
,这就是反向先行断言。
3. ?<= ...
正后发断言
正向后行断言:(?<=表达式)
,指在某个位置向左看,表示所在位置左侧必须能匹配表达式
例如:如果要取出喜欢两个字,要求喜欢的前面有我,后面有你,这个时候就要这么写:(?<=我)喜欢(?=你)
。
4. ?<!...
负后发断言
反向后行断言:(?<!表达式)
,指在某个位置向左看,表示所在位置左侧不能匹配表达式
例如:如果要取出喜欢两个字,要求喜欢的前面没有我,后面没有你,这个时候就要这么写:(?<!我)喜欢(?!你)
。
6. 标志(模式修正符:i g m)
标志也叫模式修正符,因为它可以用来修改表达式的搜索结果。 这些标志可以任意的组合使用,它也是整个正则表达式的一部分。
标志 | 描述 |
---|---|
i | 忽略大小写。 |
g | 全局搜索。 |
m | 多行修饰符:锚点元字符 ^ $ 工作范围在每行的起始。 |
1. 忽略大小写 (Case Insensitive)
修饰语 i
用于忽略大小写。 例如,表达式 /The/gi
表示在全局搜索 The
,在后面的 i
将其条件修改为忽略大小写,则变成搜索 the
和 The
,g
表示全局搜索。
"The" => The fat cat sat on the mat.
"/The/gi" => The fat cat sat on the mat.
2.全局搜索 (Global search)
修饰符 g
常用于执行一个全局搜索匹配,即(不仅仅返回第一个匹配的,而是返回全部)。 例如,表达式 /.(at)/g
表示搜索 任意字符(除了换行)+ at
,并返回全部结果。
"/.(at)/" => The fat cat sat on the mat.
"/.(at)/g" => The fat cat sat on the mat.
3.多行修饰符 (Multiline)
多行修饰符 m
常用于执行一个多行匹配。
像之前介绍的 (^,$)
用于检查格式是否是在待检测字符串的开头或结尾。但我们如果想要它在每行的开头和结尾生效,我们需要用到多行修饰符 m
。
例如,表达式 /at(.)?$/gm
表示小写字符 a
后跟小写字符 t
,末尾可选除换行符外任意字符。根据 m
修饰符,现在表达式匹配每行的结尾。
"/.at(.)?$/" => The fat
cat sat
on the mat.
"/.at(.)?$/gm" => The fat
cat sat
on the mat.
7.贪婪匹配与惰性匹配 (Greedy vs lazy matching)
正则表达式默认采用贪婪匹配模式,在该模式下意味着会匹配尽可能长的子串。我们可以使用 ?
将贪婪匹配模式转化为惰性匹配模式。
贪婪模式:会匹配最长的以开始位置开始,以结束位置结束的字符串;在线练习
懒惰模式:匹配尽可能少的字符 在线练习
示例:
贪婪模式:正则表达式<div>.*</div>
会匹配 <div>xxx</div>xxxxx<div>xxx</div>
因为 .*
意味着可以匹配大于等于0个的任意字符
,当然也包括了</div>
,也就是说会贪婪的找到全部的字符,最后再找以</div>
结尾
懒惰模式:正则表达式 <div>.*?</div>
会分别匹配 <div>xxx</div>
xxxx <div>xxx</div>
。。。
因为?
将贪婪转为了懒惰模式,也就是说每个满足规则的字符串找到第一个</div>
就停止往后找了