第1章 枚举
1.1 不使用枚举存在的问题
假设我们要定义一个人类,人类中包含姓名和性别。通常会将性别定义成字符串类型,效果如下:
public class Person {
private String name;
private String sex;
public Person() {
}
public Person(String name, String sex) {
this.name = name;
this.sex = sex;
}
// 省略get/set方法
}
public class Demo01 {
public static void main(String[] args) {
Person p1 = new Person("张三", "男");
Person p2 = new Person("张三", "abc"); // 因为性别是字符串,所以我们可以传入任意字符串
}
}
不使用枚举存在的问题:可以给性别传入任意的字符串,导致性别是非法的数据,不安全。
1.2 枚举的作用与应用场景
枚举的作用:一个方法接收的参数是固定范围之内的时候,那么就可以使用枚举。
1.3 枚举的基本语法
1.3.1 枚举的概念
枚举是一种特殊类。枚举是有固定实例个数的类型,我们可以把枚举理解成有固定个数实例的多例模式。
1.3.2 定义枚举的格式
enum 枚举名 {
第一行都是罗列枚举实例,这些枚举实例直接写大写名字即可。
}
1.3.3 入门案例
- 定义枚举:BOY表示男,GIRL表示女
enum Sex {
BOY, GIRL; // 男,女
}
- Person中的性别由String类型改为Sex枚举类型
public class Person {
private String name;
private Sex sex;
public Person() {
}
public Person(String name, Sex sex) {
this.name = name;
this.sex = sex;
}
// 省略get/set/toString方法
}
- 使用时只能传入枚举中的固定值
public class Demo02 {
public static void main(String[] args) {
Person p1 = new Person("张三", Sex.BOY);
Person p2 = new Person("张三", Sex.GIRL);
Person p3 = new Person("张三", "abc");
}
}
1.3.4 枚举的其他内容
我们大概了解了枚举类型的定义与简单使用后,现在有必要来了解一下枚举类型的基本实现原理。
实际上在使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承了Java API中的java.lang.Enum类,也就是说通过关键字enum创建枚举类型在编译后事实上也是一个类类型而且该类继承了java.lang.Enum类。
我们刚才定义的Sex枚举反编译最终效果如下:
enum Sex {
BOY, GIRL; // 男,女
}
//反编译可以看见
// 枚举的本质是一个类,我们刚才定义的Sex枚举相当于下面的类
final class Sex extends java.lang.Enum<SEX> {
public static final SEX BOY = new SEX();
public static final SEX GIRL = new SEX();
public static SEX[] values();
public static SEX valueOf(java.lang.String);
static {};
}
说明:
1)从反编译的代码可以看出编译器确实帮助我们生成了一个Sex类(注意该类是final类型的,将无法被继承)而且该类继承自java.lang.Enum类,Enum类是一个抽象类
2)除此之外,编译器还帮助我们生成了2个Sex类型的实例对象分别对应枚举中定义的2个性别,这也充分说明了我们前面使用关键字enum定义的Sex类型中的每种性别枚举常量也是实实在在的Sex实例对象。
1.3.5 枚举补充
枚举的本质是一个类,所以枚举中还可以有成员变量,成员方法等。
1)枚举的属性上面不能书写任何代码,如果属性下面有代码,需要使用分号结束属性的编写
2)只要我们使用枚举类型,那么属性属于静态的,并且给属性赋值,会创建对象,执行无参构造方法
3)如果想执行有参构造,可以给属性后面添加小括号,并赋值实际参数
4)枚举中的构造函数必须是私有的
public enum Sex {
BOY("柳岩"), GIRL(18);
public int age;
public String name;
private Sex(int age) {
System.out.println("有参构造"+age);
this.age = age;
}
private Sex() {
System.out.println("无参构造");
}
private Sex(String name) {
System.out.println("有参构造"+name);
this.name = name;
}
public void showAge() {
System.out.println("年龄是: " + age);
}
public void showName() {
System.out.println("姓名是: " + name);
}
}
public class Demo03 {
public static void main(String[] args) {
//Person p1 = new Person("张三", Sex.BOY);
// Person p2 = new Person("张三", Sex.GIRL);
//使用枚举类型获取枚举类型中的元素即对象
Sex b = Sex.BOY;
b.showName();
Sex g = Sex.GIRL;
g.showAge();
}
}
执行内容:
有参构造柳岩
有参构造18
姓名是: 柳岩
年龄是: 18
1.4 枚举的应用
枚举的作用:枚举通常可以用于做信息的分类,如性别,方向,季度等。
枚举表示性别:
public enum Sex {
MAIL, FEMAIL;
}
枚举表示方向:
public enum Orientation {
UP, RIGHT, DOWN, LEFT;
}
枚举表示季度
public enum Season {
SPRING, SUMMER, AUTUMN, WINTER;
}
1.5 小结
- 枚举类在第一行罗列若干个枚举对象,是常量。(多例模式)
- 枚举是不能在外部创建对象的,枚举的构造方法默认是私有的。
- 枚举通常用于做信息的标志和分类。
第2章 JDK8新特性
JDK8新特性:
Lambda 表达式【已学习过】
Stream API【已学习过】
方法引用
Base64
2.1方法引用
1.概念介绍
方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的代码看起来紧凑而简洁,没有很多复杂的代码。目的:就是简化代码开发的。
2.方法引用格式和分类
方法引用核心语法使用一对冒号 ::
主要有以下几类:
对象::成员方法 通过对象名引用成员方法
类名::new 类的构造方法引用
类名::静态方法 通过类名称引用静态方法
类名::成员方法 类的成员方法引用
3.方法引用基本使用
3.1通过对象名引用成员方法
格式:对象::成员方法
需求:定义函数式接口打印某个字符串。
自定义函数式接口:
@FunctionalInterface
public interface Printable {
//打印字符串str
void print(String str);
}
在Printable
接口当中唯一的抽象方法print
接收一个字符串参数,目的就是为了打印显示它。
代码实现:
package com.itheima.sh.lambda_05;
public class Demo01 {
public static void main(String[] args) {
//调用自定义方法
printString(new Printable() {
@Override
public void print(String s) {
System.out.println("s = " + s);
}
});
System.out.println("==============================");
printString(s -> System.out.println("s = " + s));
/*
这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是 `System.out`对象中的`println(String)`方法。既然Lambda希望做的事情就是调用 `println(String)`方法,那何必自己手动调用呢?
能否省去Lambda的语法格式(尽管它已经相当简洁)呢?只要“引用”过去就好了:
*/
//使用方法引用输出
printString(System.out::println);
}
//自定义方法 Printable接口作为参数
/*
Printable pt = new Printable() {
@Override
public void print(String s) {
System.out.println("s = " + s);
}
}
*/
public static void printString(Printable pt)
{
//传递参数
pt.print("Hello 上海");
}
}
小结:
1.通过对象名引用成员方法格式:对象::成员方法
2.什么情况下可以使用方法引用?
如果在一个lambda表达式中只调用一个已经存在的(就是已经定义好的)方法,这个时候可以使用方法引用来简化这种特殊的Lambda表达式。
3.2 类的构造方法引用
格式:类名::new
需求:将集合中的字符串使用Stream中的map映射方法作为Person构造方法参数转换Person对象并输出
代码演示:
public class Person {
String name; //在这里类里面只定义一个姓名
public Person(String name) {
this.name = name;
}
public Person() {
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
package com.itheima.sh.demo_05;
import java.util.ArrayList;
import java.util.stream.Stream;
public class Test02 {
public static void main(String[] args) {
//创建集合
ArrayList<String> list = new ArrayList<>();
list.add("柳岩");
list.add("石原里美");
list.add("小团团");
//Stream流就相当于是一个流水线,可以对集合进行链式编程
//获取Stream流
Stream<String> stream = list.stream();
//映射,能够把一种类型转成另一种类型
//将集合中的字符串使用map映射方法作为Person构造方法参数转换Person对象并输出
//简化写法
//stream.map(s-> new Person(s)).forEach(s-> System.out.println(s));
/*
s是一个参数,Lambda表达式的作用就是把s传递给了Person的构造方法。
这个Lambda表达式中只调用了已经存在的Person类的构造方法,在这种情况下
我们就可以使用方法引用去代替Lambda
*/
//map里面写方法引用 类名::new 表示调用这个类的构造方法
//stream.map(Person::new).forEach(s-> System.out.println(s));
stream.map(Person::new).forEach(System.out::println);
}
}
3.3 通过类名称引用静态方法
格式:类名::静态方法
由于在java.lang.Math
类中已经存在了静态方法abs
,所以当我们需要通过Lambda来调用该方法时,有两种写法。
首先是函数式接口:
@FunctionalInterface
public interface Calcable {
int calc(int num);
}
测试类代码:
public class Demo05Lambda {
public static void main(String[] args) {
//method(-10, n -> Math.abs(n));
method(-10, Math::abs);
}
private static void method(int num, Calcable lambda) {
int x = lambda.calc(num);
System.out.println(x);
}
}
小结:在这个例子中,下面两种写法是等效的:
- Lambda表达式:
n -> Math.abs(n)
- 方法引用:
Math::abs
- 上述Lambda表达式中只调用了已经存在的Math类中abs方法,所以可以使用方法引用替换。
3.4 类的成员方法引用
格式:类名::成员方法
代码演示:
public class Test03 {
public static void main(String[] args) {
//创建集合
ArrayList<String> list = new ArrayList<>();
list.add("柳岩");
list.add("石原里美");
list.add("小团团");
//Stream流就相当于是一个流水线,可以对集合进行链式编程
//获取Stream流
Stream<String> stream = list.stream();
//把字符串转成对应的长度
// stream.map((s)->{return s.length();}).forEach(System.out::println);
//这个Lambda表达式中使用参数调用了String类中的length方法
//只调用一个已经存在的方法,我们可以在这里 引用类的成员方法
//会默认的用参数s去调用String类中的length()方法
stream.map(String::length).forEach(System.out::println);
}
}
小结:类的成员方法和静态引用成员方法格式没有太大区别,但是本质上是有区别的。一个是引用非静态方法,一个是引用静态方法。
2.2遍历集合(扩展)
1单列集合Collection
jdk1.8后使用Collection接口的父接口Iterable中的默认方法:
default void forEach(Consumer<? super T> action) 对 Iterable每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。
参数:action属于Consumer消费函数式接口类型,我们这里可以使用lambda来提供Consumer接口中的抽象方法:
void accept(T t) 对给定的参数执行此操作。
代码:
package com.itheima.sh.demo_10;
import java.util.ArrayList;
import java.util.Collections;
public class Test01 {
public static void main(String[] args) {
/*
default void forEach(Consumer<? super T> action) 对 Iterable每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。
参数:action属于Consumer消费函数式接口类型,我们这里可以使用lambda来提供Consumer接口中的抽象方法:
void accept(T t) 对给定的参数执行此操作。
*/
//创建集合对象
ArrayList<String> list = new ArrayList<>();
//添加数据
Collections.addAll(list, "柳岩", "杨幂", "赵丽颖", "范冰冰");
//遍历集合list
/* list.forEach(name->{
System.out.println(name);
});*/
// list.forEach(name->System.out.println(name));
//方法引用
list.forEach(System.out::println);
}
}
2双列集合Map
jdk8以后使用Map接口中的默认方法:
default void forEach(BiConsumer<? super K,? super V> action)
BiConsumer接口中的方法:
void accept(T t, U u) 对给定的参数执行此操作。
参数
t - 第一个输入参数
u - 第二个输入参数
遍历代码:
public class Demo02 {
public static void main(String[] args) {
HashMap<String,String> m1 = new HashMap();
m1.put("刘德华", "香港");
m1.put("郭富城", "广州");
m1.forEach((key,value)->{
System.out.println(key+"---"+value);
});
}
}
2.3 Base64(了解)
-
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64里面提供了3种编码解码方式,能够在网络传输字节码时对字节进行编码和解码。
-
在Java 8中,Base64编码已经成为Java类库的标准。
-
Java 8 内置了 Base64 编码的编码器和解码器。
-
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
- **基本:**最基本编码和解码方式。
- **URL:**表示对浏览器中的地址栏进行编码和解码。编码后对特殊字符有处理,比如编码后没有/ 这个字符
- **MIME:**编码后字符串每行最多76个字符
-
基本:
使用Base64类调用类中的静态方法来获取解码器和编码器:
序号 | 方法名 & 描述 |
---|---|
1 | **static Base64.Encoder getEncoder()**返回一个 Base64.Encoder ,获取编码器 |
2 | **static Base64.Decoder getDecoder()**返回一个 Base64.Decoder ,获取解码器 |
Base64.Encoder类中的编码方法:
String encodeToString(byte[] src) 编码
Base64.Decoder类中的解码方法:
byte[] decode(String src) 解码
代码演示:
public class Test05 {
public static void main(String[] args) {
//基本方式
//获取编码器
Base64.Encoder encoder = Base64.getEncoder();
//获取解码器
Base64.Decoder decoder = Base64.getDecoder();
//演示效果
String s = "suoge123456789";
//编码
String s1 = encoder.encodeToString(s.getBytes());
System.out.println("编码前" + s ); // 编码前suoge123456789
System.out.println("编码后" + s1 ); // 编码后c3VvZ2UxMjM0NTY3ODk=
//解码
byte[] bytes = decoder.decode(s1);
System.out.println("解码后" + new String(bytes)); // 解码后suoge123456789
}
}
-
URL:
使用Base64类调用类中的静态方法来获取解码器和编码器:
序号 | 方法名 & 描述 |
---|---|
1 | **static Base64.Encoder getUrlEncoder()**返回一个 Base64.Encoder ,获取编码器 |
2 | **static Base64.Decoder getUrlDecoder()**返回一个 Base64.Decoder ,获取解码器 |
Base64.Encoder类中的编码方法:
String encodeToString(byte[] src) 编码
Base64.Decoder类中的解码方法:
byte[] decode(String src) 解码
说明:
编码后对特殊字符有处理,比如在下面的案例中,编码后没有/ 这个字符。
{“广州”:“020”}
public class Test05 {
public static void main(String[] args) {
//URL方式
//获取编码器
Base64.Encoder encoder = Base64.getUrlEncoder();
//获取解码器
Base64.Decoder decoder = Base64.getUrlDecoder();
//演示效果
String s = "{\"广州\":\"020\"}";
//编码
String s1 = encoder.encodeToString(s.getBytes());
System.out.println("编码前" + s);//编码前{"广州":"020"}
System.out.println("编码后" + s1);//编码后eyLlub_lt54iOiIwMjAifQ==
//解码
byte[] bytes = decoder.decode(s1);
System.out.println("解码后" + new String(bytes));//解码后{"广州":"020"}
}
}
- MIME:编码后字符串每行最多76个字符
使用Base64类调用类中的静态方法来获取解码器和编码器:
序号 | 方法名 & 描述 |
---|---|
1 | **static Base64.Encoder getMimeEncoder()**返回一个 Base64.Encoder ,获取编码器 |
2 | **static Base64.Decoder getMimeDecoder()**返回一个 Base64.Decoder ,获取解码器 |
Base64.Encoder类中的编码方法:
String encodeToString(byte[] src) 编码
Base64.Decoder类中的解码方法:
byte[] decode(String src) 解码
public class Test05 {
public static void main(String[] args) {
//MIME方式
//获取编码器
Base64.Encoder encoder = Base64.getMimeEncoder();
//获取解码器
Base64.Decoder decoder = Base64.getMimeDecoder();
//演示效果
String s = "";
for(int i=0; i<100; i++){
s += i;
}
//编码
String s1 = encoder.encodeToString(s.getBytes());
System.out.println("编码前" + s );
System.out.println("编码后" + s1 );
//解码
byte[] bytes = decoder.decode(s1);
System.out.println("解码后" + new String(bytes));
}
}
第3章 正则表达式
3.1正则表达式概念
在开发中,通常很多数据都会使用String类存储。原因:操作字符串的功能比较多,比较方便。在操作String类对象时,会经常遇到对字符串进行验证的功能,例如:年龄必须是2位的数字、用户名必须是8位长度而且只能包含大小写字母、数字等。而按照我们之前学习的String类,我们使用String类中的诸多函数是可以完成对字符串校验功能的,但是代码相对来说比较麻烦,所以在Java中引入正则表达式的概念来解决上述问题,即简化代码。
正则表达式:专门用于操作字符串的技术,并且可以简化代码,用于对字符串的复杂操作。
正则表达式弊端:代码可读性比较差。
3.2正则表达式引入
- 需求:验证QQ号码是否合法。
分析:
1、第一位不能是零;
2、QQ号码在5到12之间(包含);
3、QQ号码都是由数字组成;
步骤:
1)定义一个RegexDemo类,在这个类中定义一个主函数main和一个自定义函数method_1;
2)在main函数中调用自定义函数method_1;
3)在自定义函数method_1中定义一个字符串变量QQ,并赋值为12345;
4)使用if-elseIf-else结构对字符串QQ分别按照上述给定的三个条件进行判断;
5)使用String类中的charAt()函数获取字符串QQ中的第一个字符和字符0进行比较,相等则告诉用户不能以0开始;
6)使用String类中的length()函数获得字符串QQ的长度,判断是否在5~12之间;
7)验证字符串QQ中是否都是数字,使用Long.parseLong(QQ)把一个字符串转成 long 类型的数据,此函数容易发生异常,所以使用try-catch代码块对该代码进行捕获异常处理;
package cn.itcast.sh.a_regex_demo;
/*
* 需求:验证QQ号码是否合法。
分析:
1、第一位不能是零;
2、QQ号码在5到12之间(包含);
3、QQ号码都是有数字组成;
*/
public class RegexDemo {
public static void main(String[] args) {
method_1();
}
private static void method_1() {
// 定义一个字符串变量
String QQ = "12345676";
/*
* 使用判断结构判断字符串是否合法
*/
// 判断字符串的第一位是否是0 QQ.charAt(0)表示通过charAt函数根据指定的下标获取下标对应的字符
if (QQ.charAt(0) == '0') {
// 说明字符串以0开始
System.out.println("QQ号码不能以0开始");
} else if (QQ.length() < 5 || QQ.length() > 12) {
// 说明qq的号码的长度不在5~12之间
System.out.println("QQ号码的长度错误");
} else {
/*
* 说明QQ的号码一定不是以0开始,并且长度一定在5~12之间,接下来验证是否为数字
* 使用包装类Long中的parseLong()函数判断字符串中的字符是否为数字
* 如果不为数字,这个函数会抛异常ctrl+alt+T写try-catch代码块
*/
try {
long parseLong = Long.parseLong(QQ);// 将字符串转换为long类型的数字
System.out.println("QQ号码是:" + QQ);
System.out.println("QQ号码是:" + parseLong);
} catch (NumberFormatException e) {
System.out.println("QQ号码中有其他非数字的字符");
}
}
}
}
说明:
1)使用包装类Long中的parseLong()函数判断字符串中的字符是否为数字。如果不为数字,这个函数会抛异常,既然要抛异常所以写个try-catch代码块对异常进行处理;
2)对某块代码进行try-catch处理,可以按actrl+alt+T快捷键生成try-catch代码块;
-
在开发中字符串是我们使用频率最高的一类数据,针对上述程序仅仅就是为了验证一个字符串中的数据是否正确,用上述代码能够解决问题,但是代码很麻烦。在计算机语言中针对这类数据的验证和其他操作给出了更加简单的处理方案。
这个方案就是正则表达式。正则表达式它的存在就是用来简化代码的书写,方便完成对字符串的操作。
-
说明:
-
String类中提供一个boolean matches(String regex) 函数,可以判断字符串对象是否匹配正则表达式。
- 如果匹配,则返回true;
- 如果不匹配,则返回false;
-
[1-9]:表示字符串中第一位能够出现1~9任何一个数字;
-
[0-9]{4,11}:表示字符串中从第2位开始后面的数字只能出现0~9之间的数字,并且最少出现4次,最多出现11次;
-
-
将上述代码进行优化,结果如下:
package cn.itcast.sh.a_regex_demo;
/*
* 需求:验证QQ号码是否合法。
分析:
1、第一位不能是零;
2、QQ号码在5到12之间(包含);
3、QQ号码都是有数字组成;
*/
public class RegexDemo {
public static void main(String[] args) {
method_2();
}
// 使用正则表达式完成QQ号码的验证
private static void method_2() {
// 定义一个字符串变量
String QQ = "12345";
/*
* String类中提供一个matches()函数,可以判断字符串对象是否匹配正则表达式
* 如果匹配,则返回true
* 如果不匹配,则返回false
* [1-9]:表示字符串中第一位能够出现1~9任何一个数字
* [0-9]{4,11}:表示字符串中从第2位开始后面的数字只能出现0~9之间的数字,并且最少出现4次,最多出现11次
* 如果满足上述条件则返回true,否则返回false
*/
boolean flag = QQ.matches("[1-9][0-9]{4,11}");
System.out.println(flag);
}
}
小结:
1.正则表达式其实就是通过一些符号简化了代码的书写。
2.正则表达式的弊端:符号越多,阅读性越差。所以要想学习正则表达式就得先学习一些符号。
3.3正则表达式常用符号介绍(掌握)
正则表达式:正确的规则组成的一个表达式。其实就是用来简化字符串的操作。通过一些限定符号组成一种规则,来验证字符串是否符合规则
它的功能主要是用来对字符串进行各种的操作(验证、匹配、切割、替换,获取等)。
结论:正则表达式只能使用在字符串上。
学习正则表达式:主要是学习正则表达式中的常用符号。它是用我们所熟悉的 大括号、中括号 、小括号 、字母 、数字、特殊符号等代替Java代码对字符串进行操作。
在jdk6的api中有正则表达式的符号介绍:
我们使用正则表达式其中一个重要的功能就是验证字符串中每个字符是否正确:学习怎么写一个正则表达式去匹配(验证)字符串的每一位。
3.3.1 字符类
- 语法示例
[ ]表示范围的意思。表示某一位上可以出现的字符数据,如果正则中需要匹配的某个字符串中某一位上的字符是唯一的,这时可以省略中括号。
1.[abc] 表示要么是a要么是b还可以是c(只能是其中任意一个)
例:”NBA” 正则:”N[ABC]A” 匹配正确:NBA NAA NCA
2.[^abc] 当前要匹配的某个字符串中的某一位上不能是a 或b 或c(除了a,b,c都可以)
3.[a-z] 表示26个小写字母中的任意一个
4.[A-Z] 表示26个大写字母中的任意一个
5.[a-zA-Z] 表示26个大小写字母中的任意一个
6.[a-d[m-p]] 当前要匹配的某个字符串中的某一位上 可以是a - d 或 m - p 的字符
7.[a-d&&[d-f]] 表示只能是d。必须在两个范围中都要符合 。(交集)
8.[a-d&&[^d-f]] 表示只能是a,b,c
9.[a-z&&[^xyz]] 表示只能是除去x,y,z后的所有小写字母
10.[0-9] 表示0~9之间任意数字
11.[a-zA-Z0-9] 表示a-z或者A-Z或者0-9之间的任意一个字符。
-
代码示例
@Test public void show1() { String s = "d"; //字符串和正则表达式判断的方法 //1.s变量的值只能是一个字符,且必须是abc中的一个 // boolean b = s.matches("[abc]");//String s = "a"; // System.out.println("b = " + b);//true //2.s变量的值只能是一个字符,且必须是abc以外的字符 // boolean b = s.matches("[^abc]");//String s = "a"; // System.out.println("b = " + b);//false //3.s变量的值必须是两个字符,且第一个字符是abc中的一个,第二个字符是def中的一个 // boolean b = s.matches("[abc][def]");// String s = "ad"; // System.out.println("b = " + b);//true //4.[a-d&&[d-f]] 表示只能是d。必须在两个范围中都要符合 。(交集) boolean b = s.matches("[a-d&&[d-f]]");//String s = "d"; System.out.println("b = " + b);//true }
3.3.2 逻辑运算符
- 语法示例:
1. &&:并且 上面已经演示过
2. | :或者 举例:[a|b|c]:a或b或c 和[abc]效果是一样的,所以这个不用
3.3.3 预定义字符
-
语法示例
1. "." :点 表示当前需要匹配的字符串位置上可以是任意的字符。例:“a.” 以a开始后面可以是任意一个字符 2. "\d":表示数字[0-9] 3. "\D":表示非数字[^0-9] 了解 4. "\w":单词字符:[a-zA-Z_0-9]的简写 5. "\W":表示[^a-zA-Z_0-9]表示和\w相反。了解
-
代码示例
@Test public void show2() { String s = "0"; //可以匹配一个任何字符 // boolean b = s.matches(".");// String s = "a"; // System.out.println("b = " + b);// true //可以匹配一个数字 boolean b1 = s.matches("\\d"); //String s = "0"; System.out.println("b1 = " + b1);//true }
说明:
~~~java
为什么在上述正则表达式中书写\\d,而不是直接书写\d呢?
\d 代表着正则表达式中的一个符号,\和d放在一起代表0~9的十个数字。一旦将\d书写在””双引号中作为字符串,会出现一个问题,\就会把这个d转义了,一旦转义就不表示\d是一起的了,那怎么解决这种问题呢?
我们应该在\d前面在加一个\,如:\\d,第一个 \ 表示将第二个 \ 转义成普通的反斜线字符,而变成普通的反斜线之后和d组合就代表着正则中的数字,所以我们需要这样写:”\\d” 等同于”[0-9]”
3.3.4 数量词
-
语法示例
- 注意:数量词前面必须有存在正则符号。
? : 表示0次或1次 * : 表示任意次 + : 表示1次或多次 {n} : 必须出现n次 {n,} : 至少出现n次 {n,m}: 可以出现n到m次
- 代码示例
~~~java
@Test
public void show3() {
String s = "123";
//出现0次或1次单词字符 [a-zA-Z_0-9]
// boolean b = s.matches("\\w?");//String s = "0";
// System.out.println("b = " + b);//true
//出现一次或多次数字
// boolean b = s.matches("\\d+");//String s = "0";
// System.out.println("b = " + b);//true
//第一位是单词字符,后面出现2到4个数字字符
boolean b = s.matches("\\w\\d{2,4}"); //a0001-true B12-true 123-true
System.out.println("b = " + b);//true
}
3.4正则的功能介绍(掌握)
正则表达式的主要功能是用来对字符串进行操作:匹配(验证)、切割、替换、获取。
正则表达式需要和String类中的某些函数结合使用。
3.4.1 切割
- String类的split()方法原型:
public String[] split(String regex)//参数regex就是一个正则表达式。可以将当前字符串中匹配regex正则表达式的符号作为"分隔符"来切割字符串。
-
需求1:
使用String类中的split函数根据正则表达式规则,以叠词对已知的字符串进行切割。
**叠词:就是重复出现的字符。**
1)在RegexDemo2类中定义一个method_2函数;
2)在method_2函数中定义一个字符串str,并赋值为
”sfljs#######lfj234#######k454sd#####sdhd######hf”;
3)定义一个正则表达式规则:regex=”#+”;
4)使用定义好的字符串str调用split()函数对正则表达式进行切割;
5)遍历切割后的数组;
~~~java
public class RegexDemo2 {
public static void main(String[] args) {
method_2();
}
//以同一个叠词 切割
public static void method_2() {
String str = "sfljs#######lfj234#######k454sd#####sdhd######hf";
String regex = "#{2,}";
String[] split = str.split(regex);
for (int i = 0 ; i<split.length ; i++) {
//sfljs lfj234 k454sd sdhd hf
System.out.print(split[i]+” “);
}
}
- 需求2:
以叠词对已知字符串”sfljs####lfj234TTTTTTTk454sdOOOOOOOsdhd11111111hf”进行切割。
分析:这个字符串不再像我们之前做过的字符串,他比较特殊,我们之前的叠词都是一样的字符,而这个叠词中的字符都不相同,如果按照我们之前去切割是不能够实现的,那么我们该如何去切割已知的字符串呢?
我们需要借助正则中的组来完成。
正则中组的概念:
组:把已经存在的一个正则规则使用小括号封装起来,当在正则表达式中的其他位置上需要使用已经存在的正则规则的时候,这时没有必要再书写重复的规则,而直接去引用被封装好的正则规则。
例如:"([a-z_A-Z])bc[a-z_A-Z]"
上述正则表达式:在第一位和第四位上是相同的正则规则,同一正则表达式中不同位置上存在了相同规则的正则,在书写上重复没有必要。我们可以在正则表达式中的第一个位置上把[a-z_A-Z] 这个规则进行封装到一个组中。然后在正则的第四位上需要使用和第一位上相同的规则即可。 这时可以在第四位上引用这个被封装好的组。
在正则中一旦把某个规则使用小括号封装成组之后,由于我们只能使用小括号进行组的封装,而无法给组起名, 这时会自动的给这些组进行编号,组的编号从1开始,一个小括号就是一组。
如果在当前分组的这个正则表达式中引用已经存在的组,需要使用 \\组的编号
例如:"([a-z_A-Z])bc\\1"
完成上述需求:使用String类中的split函数根据正则表达式规则,以叠词对已知的字符串进行切割。(练习正则表达式中的组的概念)
1)在RegexDemo2类中定义一个method_3函数;
2)在method_3函数中定义一个字符串str,并赋值为
”sfljs####lfj234TTTTTTTk454sdOOOOOOOsdhd11111111hf”;
3)定义一个正则表达式规则:regex=”(.)\1+”;
4)使用定义好的字符串str调用split()函数对正则表达式进行切割;
5)遍历切割后的数组;
@Test
public void show5() {
String str = "sfljs####lfj234TTTTTTTk454sdOOOOOOOsdhd11111111hf";
/*
(.) 表示第一组可以是任意字符
\\1 表示使用第一组
+ 表示\\1的内容至少出现一次
*/
String regex = "(.)\\1+";
String[] arr = str.split(regex);
//[sfljs, lfj234, k454sd, sdhd, hf]
System.out.println(Arrays.toString(arr));
}
小结:
(.) 表示第一组可以是任意字符
\\1 表示使用第一组
+ 表示\\1的内容至少出现一次
2.组的练习
@Test
public void show6() {
// String s = "哈哈哈";
// //第一对儿()表示第一组,\\1表示再出现一次第一组的内容{2}表示前面的内容\\1 出现2次
// boolean b = s.matches("(.)\\1{2}"); //String s = "哈哈哈";
// System.out.println("b = " + b);//true
String s = "哼哈哈哼";
//(.)表示第一组是任意字符 (.)表示第二组是任意字符 \\2表示第二组再出现一次 \\1表示第一组再出现一次
//一个组里面可以是任意字符
boolean b = s.matches("(.)(.)\\2\\1");
System.out.println("b = " + b);//true
}
3.4.2 替换
-
String类的replaceAll()方法原型:
public String replaceAll(String regex,String newStr)//参数regex就是一个正则表达式。可以将当前字符串中匹配regex正则表达式的字符串替换为newStr。
- 操作字符串:"just do it,suoge521 nothing id impossible99"
~~~java
@Test
public void show7() {
String s = "just do it,suoge521 nothing id impossible99";
// String replace(CharSequence oldStr, CharSequence newStr)
// :用新的字符串newStr代替旧的字符串oldStr
//用*把d都替换掉
// System.out.println(s.replace("d","*"));
/*
public String replaceAll(String regex,String newStr)
也是用新的字符串替换旧的字符串,但是第一个参数可以用正则
*/
//.表示任意字符 在此时把所有字符都换成*
//结果:*******************************************
// System.out.println(s.replaceAll(".","*"));
//把每个数字换成*
//结果:just do it,suoge*** nothing id impossible**
// System.out.println(s.replaceAll("\\d","*"));
//把每段数字换成一个*
//结果:just do it,suoge* nothing id impossible*
System.out.println(s.replaceAll("\\d+","*"));
}