【Java】分组

一、分组

分组其实就是一个小括号而已,例如下图的正则表达式。

第一个是:邮箱的正则,这里我们是将红色框框起来的这一堆进行了分组,这一堆在正式的邮箱中就表示 .cn 或者 .edu.cn,这一部分是出现一次或两次。
image-20240423111932583

第二个是:身份证号码的简易正则表达式,这里就是将后面的四位分成了一组。

image-20240423112105169

那为什么要这么分呢?因为这里是有 | 符号的,如果我们没有将它分组,它就会把左边这个整体当做是第一个条件,中间的 X 是第二个条件,右边的 x 是第三个条件。

image-20240423112327084

但是我现在加上了分组,此时它就会认为:\\d 是第一个条件,中间的 X 是第二个条件,右边的 x 是第三个条件。

image-20240423112417879

第三个是:我们之前写的关于24小时时间的正则表达式

我们还是以案例来看正则:在这里,我把小时做了一个单独的分组,这里的原因也是因为在小时中有一个 |,如果这里你没有进行分组,它就会将右边的所有当做第二个条件。

image-20240423112656454

但现在我已经分组了,它就会认为 [01]\\d 是第一个条件,2[0-3] 是第二个条件,因为在小时中,有可能是 1 开头的,也有可能是 2 开头的,如果你是 1 开头的,后面可以是任意的数字;但是如果你是 2 开头的,后面只能是 0-3,两种情况是不一样的,所以我们需要分开书写。

image-20240423112832887

关于 24小时 的正则表达式我们还有一种写法:由于 :分钟:秒钟 的规则其实是一样的,既然是一样的,我们就没有必要去写两遍,我们可以把 :分钟 看做是一组,这一组一共出现两次。

image-20240423113143552

这就是我们之前写的分组,但是在分组的时候有一些小细节需要掌握一下。

二、细节

正则表达式中,每一组其实是有组号的,也就是序号,它的规则有两个。

规则1:从 1 开始,连续不间断。

规则2:以左括号为基准,最左边的是第一组,其次为第二组,以此类推。

例如:(\\d+)(\\d+)(\\d+)

以左括号为基准,即:只看左括号,不看其他的。

image-20240423113904407

现在,最左边的是第一组(红色),中间的是第二组(蓝色),右边的是第三组(绿色)。

image-20240423114056042

接下来考你个问题:下面蓝色框起来的这一组,它是第几组呢?

image-20240423114205859

正确答案:第二组。

根据规则二:以左括号为基准,因此只看左括号,不看其他的,下图用红框框起来了。

image-20240423114308718

最左的的是第一组(红色),中间的是第二组(蓝色),最右边的是第三组(绿色)。

image-20240423114413501

这个组号在正则表达式中是有实际用处的,接下来就通过几个练习来阐述。

三、捕获分组

捕获分组:把这一组的数据捕获出来,再用一次。

1)需求1:判断一个字符串的开始字符和结束字符是否一致?在这里只考虑一个字符

前面的第一个字符我不知道你写的是什么,就可以把第一个字符看做是单独的一组,结尾的时候再把第一组中的数据拿出来再用一次。

image-20240423125922286

举例: a123a b456b 17891 &abc& a123b(false)

image-20240423125723793

接下来开始写正则表达式,在写正则表达式之前先来回忆一下之前说过的技巧:拿着一个正确的数据,从左往右依次去写。

例如:a123a ,我可以将它分成三个部分 a 123 a

一部分是开始,可以是字母,可以是数字也可以是负号,也就是说可以是任意的,写一个 (.) 就行了,这就表示:首字母可以是任意的,而且我现在将首字母当成了是第一组。

String regex1 = "(.)";

第二部分是中间的,中间可以是任意的,并且至少出现一次,.+ 就OK。

String regex1 = "(.).+";

第三部分是末尾,注意,末尾就不能再写 . 了,如果你此时再写 .,就表示任意的字符,这样就跟第一组没什么关联了,在末尾得写:\\1,表示:我要把第一组的数据拿出来再用一次。

\\组号:表示把第X组的内容再拿出来用一次。

String regex1 = "(.).+\\1";
System.out.println("a123a".matches(regex1)); // true
System.out.println("b456b".matches(regex1)); // true
System.out.println("17891".matches(regex1)); // true
System.out.println("&abc&".matches(regex1)); // true
System.out.println("a123b".matches(regex1)); // false

2)需求2:判断一个字符串的开始部分和结束部分是否一致?这里可以有多个字符

需求2就是在需求1的基础上增加了一点点

举例: abc123abc b456b 123789123 &!@abc&!@ abc123abd(false)

第一部分任意的字符至少出现一次,.+ 就行了。

然后再写中间的这部分,和后面部分

String regex2 = "(.+).+\\1";
System.out.println("a123a".matches(regex1)); // true
System.out.println("b456b".matches(regex1)); // true
System.out.println("17891".matches(regex1)); // true
System.out.println("&abc&".matches(regex1)); // true
System.out.println("a123b".matches(regex1)); // false

3)需求3:判断一个字符串的开始部分和结束部分是否一致?开始部分内部每个字符也需要一致

举例: aaa123aaa bbb456bbb 111789111 &&abc&&

在书写的时候还是把它分成三部分。

首先来写开始部分,开始部分肯定也要把它分为一组,并且字符可以是任意的,因此写个 . 就行了。

但是后面有多个字符,能写 .+ 吗?肯定不行,因为 .+ 表示任意的字符至少出现一次,如果是 abc,它也是满足的;

但是题目要求了:开始部分内部每个字符也需要一致,必须要 aaa 才能满足。

那怎么办呢?

注意,当你在正则表达式中看见:XXX需要一致,此时就需要用到我们上面所说的捕获分组:要把XXX数据看做一组,然后把这一组的数据再用一次,这样才能一致,数据才能重复。

因此我们可以这么写:将第一个字符单独看做一组

String regex3 = "(.)";

而这个字符需要反复出现,所以这里可以写 \\1

String regex3 = "(.)\\1";

那到底出现多少次呢?不知道,这个是没有上限的,因此我们在后面写个 * 就行了。

* 表示 0次或多次

要注意的是,这里的 * 控制的是这里的 \\1,它表示重复的内容可以不出现,也可以出现多次。

String regex3 = "(.)\\1*";

再来写第二部分,第二部分很简单,就是 .+

String regex3 = "(.)\\1*.+";

最后一部分:将前面 (.)\\1* 这个整体,还需要再拿出来用,有的人说:这还不简单,再将这一堆括起来不就行了吗?

String regex3 = "((.)\\1*).+";

但是你要注意了,括起来之后,里面的需要就需要变成 2 了。

因为组号是以左括号为基准,最先出现的就是第一组。

String regex3 = "((.)\\2*).+";

在后面,就要写 \\1,下面就是最终的结果了

String regex3 = "((.)\\2*).+\\1";
System.out.println("aaa123aaa".matches(regex3)); // true
System.out.println("bbb456bbb".matches(regex3)); // true
System.out.println("111789111".matches(regex3)); // true
System.out.println("&&abc&&".matches(regex3)); // true
System.out.println("aaa123aab".matches(regex3)); // false

刚刚我们已经学会了正则表达式中的捕获分组,当我们在正则表达式中后续还需要使用本组内容的时候,就可以用到它了。

在正则表达式内部可以使用 \\组号 的形式来使用;但是在正则表达式的外面,它也是可以用组里面的信息的:使用的时候用 $组号 来进行体现。

4)练习:口吃替换

需求:将字符串:我要学学编编编编程程程程程程。

替换为:我要学编程

相当于将重复的字去掉,只留下一个字就行了。

题目说了替换,那肯定要用到方法。

首先定一个字符串,把原来的大串放这里

String str = "我要学学编编编编程程程程程程";

然后再用 str 去调用方法,注意调用的是 replaceAll()

如果用的是 replace(),它方法的形参叫做 target,这是不识别正则表达式的,因此需要使用下面的 replaceAll(),它的形参是 regex,是识别正则表达式的。

image-20240423140746253

写正则之前,需要先想清楚需求:把重复的内容,替换成单个的

因此现在我们要做的是,先把前面重复的东西去写一个正则表达式。

我们可以把重复部分的首字母,单独去分成一组:(.),这一组是第一组,因此就是 \\1。而

str.replaceAll("(.)");

现在我需要将第一组的拿出来再用,再用的时候出现几次比较好?是不是至少出现一次

String result = str.replaceAll("(.)\\1+");

那我们要替换成谁呢?应该替换成:第一组的内容,因此这里我需要继续使用第一组的东西,因此这里写 $1 就行了。

$ 表示:把正则表达式中第一组的内容,再拿出来用,但是由于现在是在正则表达式的外面用的,因此不能用 \\1,而是 $1

String result = str.replaceAll("(.)\\1+", "$1");
System.out.println(result); // 我要学编程

四、非捕获分组

捕获分组就是:我可以把数据用括号括起来,后续如果还要继续使用本组数据的时候,可以用 \\组号 或者 $组号 的形式再次使用。

但是在有些情况下,我仅仅是要把数据括起来,在以后不需要再使用本组的数据了,这个就叫做非捕获分组。

它的格式我们之前见过,它就是将数据仅仅括起来,而且它是不占用这里的组号的。

image-20240423145309894

非捕获分组:分组之后不需要再用本组数据,仅仅是把数据括起来,不占用组号。

身份证号码: 41080119930228457x

510801197609022309

15040119810705387X

130133197204039024

430102197606046442

下面是复制粘贴过来的 简易版校验身份证号码

String regex1 ="[1-9]\\d{16}(\\d|X|x)}";

(\\d|X|x) 中,我仅仅是想把它括起来而已,在以后,我不想用里面的数据,此时就可以在左括号的后面加一个 ?:,这种方式就叫做 非捕获分组,它的特点是:不占用组号。

String regex1 ="[1-9]\\d{16}(?:\\d|X|x)}";

但如果你后面还写一个 \\1,它就会报错。

这里 \\1 报错原因 :(?:) 就是非捕获分组,此时是不占用组号的。

也就是说此时它压根就不知道谁是第一组

image-20240423145844769

除此之外,(?:) (?=) (?!)都是非捕获分组。

但一般情况下更多的使用第一个 ?:,之前我们将 ?: 的时候曾经说了,这里的 ? 相当于前面所有数据,: 指的就是前面的数据 [1-9]\\d{16} 后面跟随的就是 \\d|X|x,那么匹配获取的时候,获取的是整体,因此更多的使用第一个 ?:

那能不能不写呢?可以不写,那为什么还要讲呢?

这是因为我们百度搜出来的一些正则表达式,还有插件中找到的正则表达式都有写,如果不讲,我们就看不懂。

五、总结

1、正则表达式中分组有两种

捕获分组、非捕获分组

2、捕获分组(默认情况)

捕获分组可以获取每组中的内容,反复使用。

在正则表达式内部可以使用 \\组号 的形式来使用;但是在正则表达式的外面,它也是可以用组里面的信息的:使用的时候用 $组号 来进行体现。

3、组号的特点

规则1:从 1 开始,连续不间断。

规则2:以左括号为基准,最左边的是第一组,其次为第二组,以此类推。

4、非捕获分组

非捕获分组:分组之后不需要再用本组数据,仅仅是把数据括起来,不占用组号。

(?:)(?=)(?!) 都是非捕获分组。

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值