admin管理员组

文章数量:821339

正则,异常、Collection、List集合

一,正则表达式

1,概述

正则表达式就是由一些特定的字符组成,代表的是一个规则。

2,书写规则

public boolean matches(String regex)判断字符串是否匹配正则表达式,匹配返回true,不匹配返回false。

正则表达式的书写的规则

符号含义举例
?0次或1次\d?
*0次或多次\d* (abc)*
+1次或多次\d+ (abc)+
{}具体次数a{7} \d{7,19}
(?i)忽略后面字符的大小写(?i)abc
a((?i)b)c只忽略b的大小写a((?i)b)c
符号含义举例
[]里面的内容出现一次[abc]
^取反[^abc]
&&交集,不能写单个的&[a-z&&m-p]
.任意字符\n 回车符号不匹配
\转义字符\d
\d0-9\d+
\D非0-9\D+
\s空白字符
\S非空白字符[^\s]
\w单词字符[a-zA-Z_0-9]
\W非单词字符[^\w]
()分组a(bc)+
|写在方括号外面表示并集ab|AB

3,应用案例

需求: 校验用户输入的电话,邮箱,时间是否合法

通过往字符串的matches()方法中传入一个正则表达式来返回一个布尔值判断符不符合要求。

这里大致的了解就行,知道方法的使用,用到正则的时候上网上搜索

// 1: 创建一个邮箱正则String yreg = "[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+";String y1 = "110@qq.com";System.out.println(y1.matches(yreg));// 2: 创建一个时间正则String sreg = "([1-9]\\d{3}-)(([0]{0,1}[1-9]-)|([1][0-2]-))(([0-3]{0,1}[0-9]))";String t1 = "2023-3-03";System.out.println(t1.matches(sreg));// 3: 创建一个中文正则String zreg = "[\u4E00-\u9FA5]+";String z1 = "你好";System.out.println(z1.matches(zreg));

4,用于查找信息

正则的另一个作用,就是爬取一个文本中想要的数据;

需求如下:

请把下面文本中的电话,邮箱,座机号码,热线都爬取出来。

电话:18512516758,18512508907
或者联系邮箱: boniu@itcast.cn
座机电话:01036517895,010-98951256
邮箱:bozai@itcast.cn,
邮箱2:dlei0009@163.com,
热线电话:400-618-9090 ,400-618-4000,
4006184000,4006189090
// 1.定义爬取规则(正则表达式)String regex = "(\\w{1,}@\\w{2,10}(\\.\\w{2,10}){1,2})|(1[3-9]\\d{9})|(0\\d{2,5}-?\\d{5,15})|400-?\\d{3,8}-?\\d{3,8}";
// 编译正则Pattern pattern = Pattern.compile(regex);Matcher matcher = pattern.matcher(data);while (matcher.find()) {String group = matcher.group();System.out.println(group);}
    18699997777boniu@itcast.cn01036517895010-98951256bozai@itcast.cndlei0009@163.com400-618-9090400-618-400040061840004006189090

5,用于搜索替换,分割内容

方法名说明
public String replaceAll(String regex , String newStr)按照正则表达式匹配的内容进行替换
public String[] split(String regex):按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。
        String str = "abc5234d678efg345hij245klm5234no534pq23rst543uvw534xy4z";
//        需求去掉字符串中的数字,知道是符合正则的条件,都给替换为空,结果如下String s = str.replaceAll("[0-9]*", "");System.out.println(s);  // abcdefghijklmnopqrstuvwxyz
        String str = "abc2d3e4f5g6h5i3j5k6lmnop3qrstuvwxyz";
// 按照数字切分String[] split = str.split("\\d");for (String s1 : split) {System.out.print(s1 +"\t");}
// abc	d	e	f	g	h	i	j	k	lmnop	qrstuvwxyz	

最后还需要了解正则中的贪婪匹配和非贪婪匹配;

需求: 我们只想要姓名

public class Regex2 {public static void main(String[] args) {String data ="欢迎张全蛋光临本系统! 他删库并跑路,欢迎李二狗子光临本系统!欢迎马六子光临本系统! " +"它浏览了很多好看的照片!欢迎夏统光临!他在六点钟购买了一台拖拉机!";String regex1 = "欢迎(.+)光临";   // .+是贪婪匹配,尽量多的匹配,从后往前,还个字符匹配String regex2 = "欢迎(.+?)光临";  // .+?是非贪婪匹配(懒模式) 尽量小的匹配,从前往后,还个字符匹配Pattern compile = Pattern.compile(regex2);Matcher matcher = compile.matcher(data);while (matcher.find()) {
//            这里一个小括号是一组String group = matcher.group(1);System.out.println(group);}

什么意思呢,贪婪也就是整个的去匹配,直接看结果,如果这里compile()中使用的是regex1,结果如下:

// 张全蛋光临本系统! 他删库并跑路,欢迎李二狗子光临本系统!欢迎马六子光临本系统! 它浏览了很多好看的照片!欢迎夏统

比较贪婪嘛,返回的是第一个光临开始到最后一个光临结束的字符串。

而如果是非贪婪匹配就是离自己最近的,使用regex2 结果如下:

张全蛋
李二狗子
马六子
夏统

最后还有一点,先看需求:有重复的数据的时候,怎么去重呢?在replaceAll中传如下正则,这里我不理解。但是老师讲到了,老师说是固定的,所以这里就先记着有这样的一个方法,等到用的时候能够想起来就行

//        有重复的数据的时候,怎么去重呢?String str = "我我我我爱爱爱爱java";String res = str.replaceAll("(.)\\1+","$1");System.out.println(res);   // 我爱java

二,异常

1,认识异常

通过前面的学习,会有这样的一个问题,调用一个方法时,经常一部小心就出异常了,然后在控制台打印一些异常信息。其实打印的这些异常信息,就叫做异常。

因为写代码时经常会出现问题,Java的设计者们早就为我们写好了很多个异常类,来描述不同场景下的问题。而有些类是有共性的所以就有了异常的继承体系

抛出异常(throws)

在方法上使用throws关键字,可以将方法内部出现的异常抛出去给调用者处理。

方法 throws 异常1 ,异常2 ,异常3 ..{…}

捕获异常(try…catch)

直接捕获程序出现的异常。

try{
// 监视可能出现异常的代码!
}catch(异常类型1 变量){
// 处理异常
}catch(异常类型2 变量){
// 处理异常
}...

先来演示一个运行时异常产生

int[] arr = {11,22,33};
//5是一个不存在的索引,所以此时产生ArrayIndexOutOfBoundsExcpetion
System.out.println(arr[5]); 

对于程序中的异常我们通常有两种解决方案。

  • 第一种:使用throws在方法上声明,意思就是告诉下一个调用者,这里面可能有异常啊,你调用时注意一下。
// throws ParseException    
public static void main(String[] args) throws ParseException{SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date d = sdf.parse("2028-11-11 10:24");System.out.println(d);
}
  • 第二种:使用try…catch语句块异常进行处理。
public static void main(String[] args) throws ParseException{try {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date d = sdf.parse("2028-11-11 10:24");System.out.println(d);} catch (ParseException e) {e.printStackTrace();}}

2,自定义异常

尽管java 的编写这个提供了很多的异常类,但是无法为这个世界上的全部问题都提供异常类,如果企业自己的某种问题,想通过异常来表示,那就需要自己来定义异常类了。通过一个需求,来自定义异常

需求:写一个saveAge(int age)方法,在方法中对参数age进行判断,如果age<0 或者 >=130就认为年龄不合法,如果年龄不合法,就给调用者抛出一个年龄非法异常。

Java的API中是没有年龄非常这个异常的,所以我们可以自定义一个异常类,用来表示年龄非法异常,然后再方法中抛出自定义异常即可。

// 1、必须让这个类继承自Exception,才能成为一个编译时异常类。
public class AgeIllegalException extends Exception{public AgeIllegalException() {}public AgeIllegalException(String message) {super(message);}
}
  • 写一个测试类,在测试类中定义一个saveAge(int age)方法,对age判断如果年龄不在0~130之间,就抛出一个AgeIllegalException异常对象给调用者。
public class ExceptionTest2 {public static void main(String[] args) {// 需求:保存一个合法的年龄try {saveAge2(225);System.out.println("saveAge2底层执行是成功的!");} catch (AgeIllegalException e) {e.printStackTrace();System.out.println("saveAge2底层执行是出现bug的!");}}//2、在方法中对age进行判断,不合法则抛出AgeIllegalExceptionpublic static void saveAge(int age){if(age > 0 && age < 150){System.out.println("年龄被成功保存: " + age);}else {// 用一个异常对象封装这个问题// throw 抛出去这个异常对象throw new AgeIllegalRuntimeException("/age is illegal, your age is " + age);}}
}

这个特殊说明一下: 自定义异常可能是编译时异常,也可以是运行时异常

1.如果自定义异常类继承Excpetion,则是编译时异常。特点:方法中抛出的是编译时异常,必须在方法上使用throws声明,强制调用者处理。2.如果自定义异常类继承RuntimeException,则运行时异常。特点:方法中抛出的是运行时异常,不需要在方法上用throws声明。

3,异常的处理

有如下的场景:A调用B,B调用C;C中有异常产生抛给B,B中有异常产生又抛给A;异常到了A这里就不建议再抛出了,因为最终抛出被JVM处理程序就会异常终止,并且给用户看异常信息,用户也看不懂,体验很不好。

此时比较好的做法就是:1.将异常捕获,将比较友好的信息显示给用户看;2.尝试重新执行,看是是否能修复这个问题。

  • 第一种处理方式是,在main方法中对异常进行try…catch捕获处理了,给出友好提示。(只看过程)
    public static void main(String[] args)  {try {test1();} catch (FileNotFoundException e) {System.out.println("您要找的文件不存在!!");e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。} catch (ParseException e) {System.out.println("您要解析的时间有问题了!");e.printStackTrace(); // 打印出这个异常对象的信息。记录下来。}}public static void test1() throws FileNotFoundException, ParseException {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date d = sdf.parse("2028-11-11 10:24:11");System.out.println(d);test2();}public static void test2() throws FileNotFoundException {// 读取文件的。InputStream is = new FileInputStream("D:/meinv.png");}
  • 第二种处理方式是:在main方法中对异常进行捕获,并尝试修复
 public static void main(String[] args) {// 需求:调用一个方法,让用户输入一个合适的价格返回为止。// 尝试修复while (true) {try {System.out.println(getMoney());break;} catch (Exception e) {System.out.println("请您输入合法的数字!!");}}}public static double getMoney(){Scanner sc = new Scanner(System.in);while (true) {System.out.println("请您输入合适的价格:");double money = sc.nextDouble();if(money >= 0){return money;}else {System.out.println("您输入的价格是不合适的!");}}}

三,集合

1,集合的分类

集合就像是一个容器,用来存储我们想存储的数据。根据集合中元素存储的特点进行分类学习,如下图所示:一类是单列集合元素是一个一个的,另一类是双列集合元素是一对一对的。

下面的笔记先记录Collection集合中的方法。

通过这个图我们可以看到,有两个接口实现了Collection接口,分别为Set接口和List接口;既然Collection是他们两个的父接口,所以定义的抽象方法,肯定是,他们两个能够通用的。下面先对这两个实现接口主要的特点做一个简单的介绍。

3,Collection集合中常用的方法

List集合: 集合中的元素有序<指存入时的顺序和取出的顺序一致>,2,集合中的元素可以重复,3,集合中的元素可以通过索引获取

Set集合(相反):集合中的元素无序,元素不可重复,不可通过索引获取

所以作为他们的父接口的抽象方法,必然是兼顾二者的方法;如下:

方法名说明
public boolean add(E e)把给定的对象添加到当前集合中
public void clear()清空集合中所有的元素
public boolean remove(E e)把给定的对象在当前集合中删除
public boolean contains(Object obj)判断当前集合中是否包含给定的对象
public boolean isEmpty()判断当前集合是否为空
public int size()返回集合中元素的个数。
public Object[] toArray()把集合中的元素,存储到数组中
Collection<String> c = new ArrayList<>();
//1.public boolean add(E e): 添加元素到集合
c.add("java1");
c.add("java1");
c.add("java2");
c.add("java2");
c.add("java3");
System.out.println(c); //打印: [java1, java1, java2, java2, java3]//2.public int size(): 获取集合的大小
System.out.println(c.size()); //5//3.public boolean contains(Object obj): 判断集合中是否包含某个元素
System.out.println(c.contains("java1")); //true
System.out.println(c.contains("Java1")); //false//4.pubilc boolean remove(E e): 删除某个元素,如果有多个重复元素只能删除第一个
System.out.println(c.remove("java1")); //true
System.out.println(c); //打印: [java1,java2, java2, java3]//5.public void clear(): 清空集合的元素
c.clear(); 
System.out.println(c); //打印:[]//6.public boolean isEmpty(): 判断集合是否为空 是空返回true 反之返回false
System.out.println(c.isEmpty()); //true//7.public Object[] toArray(): 把集合转换为数组
Object[] array = c.toArray();
System.out.println(Arrays.toString(array)); //[java1,java2, java2, java3]//8.如果想把集合转换为指定类型的数组,可以使用下面的代码
String[] array1 = c.toArray(new String[c.size()]);
System.out.println(Arrays.toString(array1)); //[java1,java2, java2, java3]//9.还可以把一个集合中的元素,添加到另一个集合中
Collection<String> c1 = new ArrayList<>();
c1.add("java1");
c1.add("java2");
Collection<String> c2 = new ArrayList<>();
c2.add("java3");
c2.add("java4");
c1.addAll(c2); //把c2集合中的全部元素,添加到c1集合中去
System.out.println(c1); //[java1, java2, java3, java4]

即ArrayList、LinkedList、vector、HashSet、LinkedHashSet、TreeSet集合都可以调用下面的方法。

四,Collection的遍历

有这样的一个动物类;用于编号id,姓名name这两个属性,包括对应的toString ,setter,getter方法,有参和无参构造,主要代码如下:

class Animal{private String id;private String name;// toString ,setter,getter方法,有参和无参构造,,,,省略
}

有这样一个测试类,在主方法中创建Animal中的元素:通过下面的几个方法来学习遍历集合

public static void main(String[] args) {Collection<Animal> animals = new ArrayList<>();animals.add(new Animal("1001","薛定谔的猫"));animals.add(new Animal("1002","芝诺的乌龟"));animals.add(new Animal("1003","麦克斯韦妖"));animals.add(new Animal("1004","拉普拉斯兽"));}

可能会想到最原始的遍历,for循环,这样就错了,因为Collection是Set的父类,所以必须要做到兼容,就不可能做到通过索引的方式来获取,只能通过特殊的方法,也就是下面的迭代器。

1,迭代器遍历集合

通过刚才的结构图中我们可以看到,Collection是Iterator的子类,所以就拥有了父类的迭代器的功能,在遍历的时候就可以使用。

先介绍Collection接口中获取迭代器的方法:

方法名称说明
Iterator iterator()返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素

再介绍迭代器中的两个主要的方法:

方法名称说明
boolean hasNext()询问当前位置是否有元素存在,存在返回true ,不存在返回false
E next()获取当前位置的元素,并同时将迭代器对象指向下一个元素处。

那么我们就可以使用了,

        
// 通过集合对象调用Iterator方法来获取一个迭代器,然后通过迭代中的两个方法,来实现遍历
Iterator<Animal> iterator = animals.iterator();
while (iterator.hasNext()) {System.out.println(iterator.next());
}

注意:在使用迭代器的时候,不要去增加或者删除元素,这样很容易造成异常。也不要在一次循环中,通过next方法获取两次对象,否则也可能出问题,因为next调用一次就会向下一个数据挪动。

比如:如果说是最后一个hasNext判断为true,能够进去,第一次使用next获取数据后,指针会向后移动,但是后面已经没有元素了,所以在下一次再去执行next的时候,就会发生异常。

2,增强for遍历

底层使用的还是迭代器

    public Iterator<E> iterator() {return new Itr();}

格式:

for (元素的数据类型 变量名 : 数组或者集合) {}

增强for可以用来遍历集合或者数组。

增强for遍历集合,本质就是迭代器遍历集合的简化写法

for (Animal animal : animals) {System.out.println(animal);
}

3,forEach遍历集合

得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的方式来遍历集合。

方法名称说明
default void forEach(Consumer<? super T> action)结合lambda遍历集合

这个参数是一个函数式接口: 使用lambda方式如下: 网上对其的具体分析:【Java 8 新特性】Java Consumer示例_功能型接口consumer_猫巳的博客-CSDN博客

animals.forEach(animal -> System.out.println(animal));

五,List集合

1,特点,特有的方法

List系列集合特点: 有序,可重复,有索引

ArrayList:有序,可重复,有索引。

LinkedList:有序,可重复,有索引。

List集合因为支持索引,所以多了很多与索引相关的方法,当然,Collection的功能List也都继承了。

方法名称说明
void add(int index,E element)在此集合中的指定位置插入指定的元素
E remove(int index)删除指定索引处的元素,返回被删除的元素
E set(int index,E element)修改指定索引处的元素,返回被修改的元素
E get(int index)返回指定索引处的元素

2, List集合的遍历方式

List集合相比于前面的Collection多了一种可以通过索引遍历的方式,所以List集合遍历方式一共有四种:

  • 普通for循环(只因为List有索引)
  • 迭代器
  • 增强for
  • Lambda表达式
List<String> list = new ArrayList<>();
list.add("java");
list.add("PHP");
list.add("Python");//1.普通for循环
for(int i = 0; i< list.size(); i++){//i = 0, 1, 2String e = list.get(i);System.out.println(e);
}//2.增强for遍历
for(String s : list){System.out.println(s);
}//3.迭代器遍历
Iterator<String> it = list.iterator();
while(it.hasNext()){String s = it.next();System.out.println(s);
}//4.lambda表达式遍历
list.forEach(s->System.out.println(s));

3,ArrayList集合的底层原理

ArrayList集合底层是基于数组结构实现的,也就是说往集合容器中存储元素时,底层本质上是往数组中存储元素。 特点如下:

记住他的特点,可以根据具体的环境选择不同的集合以提高程序的效率

我们知道数组的长度是固定的,但是集合的长度是可变的,这是怎么做到的。原理如下:

数组扩容,并不是在原数组上扩容(原数组是不可以扩容的),底层是创建一个新数组,然后把原数组中的元素全部复制到新数组中去。

4,LinkedList底层原理

LinkedList底层是链表结构,链表结构是由一个一个的节点组成,一个节点由数据值、下一个元素的地址组成。如下图所示

现在要在B节点和D节点中间插入一个元素,只需要把B节点指向D节点的地址断掉,重新指向新的节点地址就可以了。如下图所示:

现在想要把D节点删除,只需要让C节点指向E节点的地址,然后把D节点指向E节点的地址断掉。此时D节点就会变成垃圾,会把垃圾回收器清理掉。

上面的链表是单向链表,它的方向是从头节点指向尾节点的,只能从左往右查找元素,这样查询效率比较慢;还有一种链表叫做双向链表,不光可以从做往右找,还可以从右往左找。如下图所示:

LinkedList集合是基于双向链表实现的,所以相对于ArrayList新增了一些可以针对头尾进行操作的方法,如下图示所示:

方法名称说明
public void addFirst(E e)在该列表开头插入指定的元素
public void addLast(E e)将指定的元素追加到此列表的末尾
public E getFirst()返回此列表中的第一个元素
public E getLast()返回此列表中的最后一个元素
public E removeFirst()从此列表中删除并返回第一个元素
public E removeLast()从此列表中删除并返回最后一个元素

因为LinkedList底层是双向链表,所以下面的特点就很好理解了

特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的。

应用场景: 队列,栈 实例代码如下:

//1.创建一个队列:先进先出、后进后出
LinkedList<String> queue = new LinkedList<>();
//入对列
queue.addLast("第1号人");
queue.addLast("第2号人");
queue.addLast("第3号人");
queue.addLast("第4号人");
System.out.println(queue);//出队列
System.out.println(queue.removeFirst());	//第4号人
System.out.println(queue.removeFirst());	//第3号人
System.out.println(queue.removeFirst());	//第2号人
System.out.println(queue.removeFirst());	//第1号人
//1.创建一个栈对象
LinkedList<String> stack = new ArrayList<>();
//压栈(push) 等价于 addFirst()
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack); //[第4颗子弹, 第3颗子弹, 第2颗子弹,第1颗子弹]//弹栈(pop) 等价于 removeFirst()
System.out.println(statck.pop()); //第4颗子弹
System.out.println(statck.pop()); //第3颗子弹
System.out.println(statck.pop()); //第2颗子弹
System.out.println(statck.pop()); //第1颗子弹//弹栈完了,集合中就没有元素了
System.out.println(list); //[]

5,Vector

Vector底层和ArrayLlist底层一样也是一个对象数组,

protected Object[] elementData;

Vector是线程同步的,即线程安全的 操作方法带有 synchronized 关键字(先了解)

在开发中需要线程同步安全的时候,考虑使用 Vector

底层结构版本线程安全(同步)效率扩容倍数
ArrayList可变数组jdk1.2不安全,效率高如果有参构造1.5倍 如果无参 1,第一次是10 2,第二次是1.5倍扩容
Vector可变数组1.0安全,效率不高如果无参默认是10,满后,就按照两倍扩容 如果指定大小,则每次直接按两倍扩

本文标签: 正则,异常CollectionList集合