今日内容
- 多线程------------->必须掌握
- 线程的状态
- 等待唤醒机制
- Lambda表达式------------->建议掌握
- Lambda表达式的使用场景
- Lambda表达式的格式(标准\省略)
- Stream流
- 流式思想的概述
- 使用Stream流------------->建议掌握
- 获取流–>操作流–>收集结果
第一章 线程状态
1.1 线程状态
线程状态概述
线程由生到死的完整过程:技术素养和面试的要求。
线程从创建到销毁的过程称为线程的生命周期,在线程的生命周期内一共有六种状态:
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程对象,没有线程特征。创建线程对象时 |
Runnable(可运行) | 调用了 start() 方法,此时线程可能正在执行,也可能没有,这取决于操作系统的调度。调用start方法时 |
Blocked(锁阻塞) | 当线程试图获取锁对象,而该锁对象被其他的线程持有,则该线程进入锁阻塞状态;当该线程获取到锁对象时,该线程将变成可运行状态。等待锁对象时 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。锁对象调用wait()方法时 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。调用sleep()方法时 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。run方法执行结束时 |
线程状态的切换
我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题。
1.2 计时等待和无限等待
-
计时等待: 调用线程类的 sleep() 方法可使当前线程进入睡眠状态,当睡觉时间达到时线程会被自动唤醒。
-
public static void sleep(long time)
让当前线程进入到睡眠状态,到毫秒后自动醒来继续执行/** * Created by PengZhiLin on 2021/8/9 9:35 */ public class Test { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { System.out.println("Hello World "+i); // 暂停1秒 Thread.sleep(1000); } } }
-
-
无限等待
-
Object类的方法:
public void wait()
: 让当前线程进入到等待状态 此方法必须锁对象调用.public void notify()
: 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.public void notifyAll()
: 唤醒当前锁对象上所有等待状态的线程 此方法必须锁对象调用.
-
案例1: 无限等待线程
/** * Created by PengZhiLin on 2021/8/9 9:39 */ public class Test1 { // 锁对象 static Object lock = new Object(); public static void main(String[] args) { // 创建并启动线程 new Thread(new Runnable() { @Override public void run() { synchronized (lock) { System.out.println("线程1: 获取锁对象,调用wait方法,准备进入无线等待..."); // 进入无限等待 try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程1: 被唤醒了,并获取了锁对象,继续执行..."); } } }).start(); } }
-
案例2: 等待和唤醒案例
package com.itheima.demo2_无限等待;
/**
- Created by PengZhiLin on 2021/8/9 9:39
*/
public class Test2 { // 锁对象 static Object lock = new Object(); public static void main(String[] args) { // 创建并启动线程--->等待 new Thread(new Runnable() { @Override public void run() { synchronized (lock) { System.out.println("线程1: 获取锁对象,调用wait方法,准备进入无线等待..."); // 进入无限等待 try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程1: 被唤醒了,并获取了锁对象,继续执行..."); } } }).start(); // 创建并启动线程--->唤醒 new Thread(new Runnable() { @Override public void run() { synchronized (lock) { // 唤醒无限等待线程 System.out.println("线程2: 3秒后唤醒无限等待线程..."); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } lock.notify(); } } }).start(); } }
- 等待唤醒: - 必须使用锁对象调用wait()\notfiy()\notifyAll()方法 - 调用wait()\notfiy()\notifyAll()方法的锁对象必须一致
-
1.3 等待唤醒机制
什么是等待唤醒机制
- 概述: 使用等待和唤醒实现多条线程之间有规律的执行
- 例如: 子线程打印i循环,主线程打印j循环
- 不使用等待唤醒机制: 结果是主线程和子线程随机交替打印输出----->没有规律
- 使用等待唤醒机制: 结果就要有规律的打印输出
- 打印1次i循环,然后打印1次j循环…依次循环打印输出…---->有规律
- 如何实现:
- 子线程打印1次i循环,然后唤醒主线程来执行, 子线程就进入无限等待
- 主线程打印1次j循环,然后唤醒子线程来执行,主线程就进入无限等待
- 子线程打印1次i循环,然后唤醒主线程来执行,子线程就进入无限等待
- 主线程打印1次j循环,然后唤醒子线程来执行,主线程就进入无限等待
- …
如何实现等待唤醒机制:
-
1.使用锁对象调用wait()方法进入无限等待
-
2.使用锁对象调用notify()方法唤醒无限等待线程
-
3.调用wait(),notify()方法的锁对象要一致
-
案例: 主线程和子线程有规律的交替打印输出
/** * Created by PengZhiLin on 2021/8/9 9:55 */ public class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { synchronized (Test.lock) { // 条件(flag=true): 子线程进入无限等待 if (Test.flag == true) { try { Test.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 条件(flag=false): 子线程被唤醒,执行任务代码 if (Test.flag == false) { System.out.println("子线程:第" + i + "次i循环"); // 唤醒主线程 Test.lock.notify(); // 修改旗帜变量的值 Test.flag = true; } } } } }
/** * Created by PengZhiLin on 2021/8/9 9:55 */ public class Test { // 共享锁对象 static Object lock = new Object(); // 共享条件变量 static boolean flag = false; public static void main(String[] args) { // 创建并启动子线程 new MyThread().start(); // 主线程任务 for (int j = 0; j < 100; j++) { synchronized (lock) { // 条件(flag=false): 主线程进入无限等待 if (flag == false) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 条件(flag=true): 主线程被唤醒,执行任务代码 if (flag == true) { System.out.println("主线程:第" + j + "次j循环"); // 唤醒子线程 lock.notify(); // 修改旗帜变量的值 flag = false; } } } } }
分析等待唤醒机制程序的执行
- 1.不管是否使用等待唤醒机制,线程的调度都是抢占式
- 2.线程进入无限等待,线程就会释放锁,cpu,也不会再去争夺
- 3.唤醒其他线程,当前唤醒线程是不会释放锁,cpu的
- 4.无限等待线程被唤醒,拿到锁对象后,会从进入无限等待的位置继续往下执行
1.4 等待唤醒案例
需求
-
等待唤醒机制其实就是经典的“生产者与消费者”的问题。
-
就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:
分析
包子铺线程生产包子,生产完了,包子就有了,唤醒吃货线程来吃包子,然后包子铺线程进入无限等待;
吃货线程吃包子,吃完了,包子就没有了,唤醒包子铺线程来生产包子,然后吃货线程进入无限等待;
包子铺线程生产包子,生产完了,包子就有了,唤醒吃货线程来吃包子,然后包子铺线程进入无限等待;
吃货线程吃包子,吃完了,包子就没有了,唤醒包子铺线程来生产包子,然后吃货线程进入无限等待;
.....
共享旗帜变量(有包子:true,没有包子:false)
包子铺线程:
有包子,就进入无限等待
没有包子,就执行任务代码(生产包子),唤醒吃货线程,修改包子的状态
吃货线程:
没有包子,就进入无限等待
有包子,就执行任务代码(吃包子),唤醒包子铺线程,修改包子的状态
实现
-
包子铺线程
/** * Created by PengZhiLin on 2021/8/9 10:15 */ public class BaoZiPu extends Thread { @Override public void run() { // 循环生产包子 while (true) { synchronized (Test.lock) { //有包子,就进入无限等待 if (Test.flag == true) { try { Test.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //没有包子,就执行任务代码(生产包子),唤醒吃货线程,修改包子的状态 if (Test.flag == false) { System.out.println("包子铺线程: 开始做包子..."); System.out.println("包子铺线程: 包子做好了,吃货快来吃包子..."); // 唤醒吃货线程 Test.lock.notify(); //修改包子的状态 Test.flag = true; } } } } }
-
吃货线程
/** * Created by PengZhiLin on 2021/8/9 10:15 */ public class ChiHuo extends Thread { @Override public void run() { // 循环吃包子 while (true) { synchronized (Test.lock) { //没有包子,就进入无限等待 if (Test.flag == false) { try { Test.lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //有包子,就执行任务代码(吃包子),唤醒包子铺线程,修改包子的状态 if (Test.flag == true) { System.out.println("吃货线程:开始吃包子..."); System.out.println("吃货线程:吃完了包子,包子铺快来做包子..."); // 唤醒包子铺线程 Test.lock.notify(); //修改包子的状态 Test.flag = false; } } } } }
-
测试类
/** * Created by PengZhiLin on 2021/8/9 10:15 */ public class Test { // 共享锁对象 static Object lock = new Object(); // 共享旗帜变量 static boolean flag = false; public static void main(String[] args) { // 创建并启动线程 new BaoZiPu().start(); new ChiHuo().start(); } }
第二章 Lambda表达式
2.1 函数式编程思想概述
面向对象编程思想
面向对象强调的是对象 , “必须通过对象的形式来做事情”,相对来讲比较复杂,有时候我们只是为了做某件事情而不得不创建一个对象 , 例如线程执行任务,我们不得不创建一个实现Runnable接口对象,但我们真正希望的是将run方法中的代码传递给线程对象执行
函数编程思想
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。例如线程执行任务 , 使用函数式思想 , 我们就可以通过传递一段代码给线程对象执行,而不需要创建任务对象
2.2 Lambda表达式的体验
-
实现Runnable接口的方式创建线程执行任务
-
匿名内部类方式创建线程执行任务
- 以上2种方式,其实都是通过Runnable的实现类对象来传递任务给线程执行
- 思考: 是否能不通过实现类对象传递任务给线程执行呢?—>函数式编程
-
Lambda方式创建线程执行任务
/** * Created by PengZhiLin on 2021/8/9 10:39 */ public class Test { public static void main(String[] args) { // 需求:创建并启动线程,执行任务代码 // 面向对象: new Thread(new Runnable() { @Override public void run() { System.out.println("线程任务代码1..."); } }).start(); // 函数式编程: new Thread(()->System.out.println("线程任务代码2...")).start(); } }
-
Lambda表达式没有特殊功能,就是用来简化代码的
2.3 Lambda表达式的标准格式
-
标准格式
- 格式:
(参数类型 参数名,参数类型 参数名,...)->{ 代码块 }
- 格式:
-
Lambda的使用前提条件:
-
接口中只有一个抽象方法的接口,才可以使用Lambda表达式
-
只有一个抽象方法的接口叫做函数式接口,函数式接口可以使用@FunctionalInterface注解标识
-
eg:
@FunctionalInterface interface A{ void method(); } @FunctionalInterface interface B{ void method1(); default void method2(){ System.out.println("默认方法..."); } } //@FunctionalInterface // 编译报错 interface C{ void method1(); void method2(); }
-
-
格式说明
- 小括号中的参数类型,参数个数,参数顺序要和函数式接口中抽象方法的形参列表一致
- -> 固定格式,表示指向
- 大括号中的内容其实就是之前重写接口中抽象方法的方法体
-
案例演示
-
Runnable函数式接口
-
Comparator函数式接口
/** * Created by PengZhiLin on 2021/8/9 10:48 */ public class Test { public static void main(String[] args) { // 创建并启动线程 new Thread(()->{ System.out.println("任务代码.."); System.out.println("任务代码.."); System.out.println("任务代码.."); System.out.println("任务代码.."); }).start(); // 创建ArrayList集合,限制集合元素类型为Integer ArrayList<Integer> list = new ArrayList<>(); // 添加元素 list.add(500); list.add(100); list.add(400); list.add(200); list.add(300); System.out.println("排序前:"+list); // 指定规则排序: 降序 /* Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } });*/ Collections.sort(list,(Integer o1, Integer o2)->{ return o2 - o1;}); System.out.println("排序后:"+list); } }
-
-
Lambda使用套路
- 1.判断该位置上是否可以使用Lambda表达式—>使用前提
- 2.如果可以使用,直接写上()->{}
- 3.填充小括号中的内容—>函数式接口中抽象方法的形参一致
- 4.填充大括号中的内容—>重写函数式接口抽象方法需要的方法体
2.4 Lambda表达式省略格式
-
省略规则
- 小括号中参数类型可以省略不写
- 小括号中只有一个参数,那么小括号也可以省略
- 大括号中如果只有一条语句,那么大括号,return关键字,分号也可以省略(三个要一起省略)
-
案例演示
/** * Created by PengZhiLin on 2021/8/9 10:48 */ public class Test { public static void main(String[] args) { // 创建并启动线程 // 标准格式 new Thread(() -> { System.out.println("任务代码1.."); }).start(); // 省略格式 new Thread(() -> System.out.println("任务代码2..") ).start(); // 创建ArrayList集合,限制集合元素类型为Integer ArrayList<Integer> list = new ArrayList<>(); // 添加元素 list.add(500); list.add(100); list.add(400); list.add(200); list.add(300); System.out.println("排序前:" + list); // 指定规则排序: 降序 // 标准格式 /*Collections.sort(list, (Integer o1, Integer o2) -> { return o2 - o1; });*/ // 省略格式 Collections.sort(list, (o1, o2) -> o2 - o1); System.out.println("排序后:" + list); } }
2.5 Lambda的表现形式
-
Lambda的表现形式: Lambda表达式会出现在哪些位置
-
变量形式: 赋值一个Lambda表达式
-
参数形式: 传入Lambda表达式作为实参
-
返回值形式: 返回一个Lambda表达式(返回值)
-
案例:
/** * Created by PengZhiLin on 2021/8/9 11:01 */ public class Test { public static void main(String[] args) { // 变量形式 Runnable r = ()->{System.out.println("任务代码1...");}; new Thread(r).start(); // 参数形式 new Thread(()->{System.out.println("任务代码2...");}).start(); // 返回值形式 Runnable runnable = getRunnable(); new Thread(runnable).start(); } public static Runnable getRunnable(){ return ()->{System.out.println("任务代码3...");}; } }
-
-
第三章 Stream
在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。
3.1 体验Stream流的使用
-
传统方式操作集合
-
需求:
List<String> one = new ArrayList<>(); one.add("迪丽热巴"); one.add("宋远桥"); one.add("苏星河"); one.add("老子"); one.add("庄子"); one.add("黄祺龙"); one.add("孙子"); one.add("洪七公"); 需求: 1. 队伍中只要名字为3个字的成员姓名; 2. 队伍中筛选之后只要前3个人;
-
-
Stream流操作集合
/** * Created by PengZhiLin on 2021/8/9 11:06 */ public class Test1 { public static void main(String[] args) { List<String> one = new ArrayList<>(); one.add("迪丽热巴"); one.add("宋远桥"); one.add("苏星河"); one.add("老子"); one.add("庄子"); one.add("张无忌"); one.add("孙子"); one.add("洪七公"); //1. 队伍中只要名字为3个字的成员姓名; // 1.1 创建一个新的集合用来存储名字为3个字的姓名 ArrayList<String> list1 = new ArrayList<>(); // 1.2 循环遍历集合,进行筛选 for (String name : one) { if (name.length() == 3) { list1.add(name); } } System.out.println("list1:" + list1); //2. 队伍中筛选之后只要前3个人; // 2.1 创建一个新的集合用来存储名字为3个字的姓名 ArrayList<String> list2 = new ArrayList<>(); // 2.2 循环获取前三个姓名 for (int i = 0; i < 3; i++) { String name = list1.get(i); list2.add(name); } System.out.println("list2:" + list2); // Stream流的使用 // 获取流---->操作流 one.stream().filter(name->name.length()==3).limit(3).forEach(name-> System.out.println(name)); } }
3.2 流式思想概述
-
概述: 可以将流式思想类比成工厂车间的流水线\河流…
-
特点:
- 流一定要搭建好完整的函数模型,函数模型中必须要有终结方法
- Stream流不能重复操作,也就是一个Stream流只能使用一次
- Stream流不会存储数据的
- Stream流不会修改数据源
3.3 获取流方式
-
根据集合获取流---->Collection集合中有一个获取流的方法
public default Stream<E> stream();
-
根据Collection获取流
/** * Created by PengZhiLin on 2021/8/9 11:39 */ public class Test { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("迪丽热巴"); list.add("宋远桥"); list.add("苏星河"); list.add("老子"); // 通过List集合获取流 Stream<String> stream1 = list.stream(); stream1.forEach(name-> System.out.println(name)); System.out.println("------------------"); Set<String> set = new HashSet<>(); set.add("庄子"); set.add("张无忌"); set.add("孙子"); set.add("洪七公"); // 通过Set集合获取流 Stream<String> stream2 = set.stream(); stream2.forEach(name-> System.out.println(name)); } }
-
根据Map获取流
-
根据Map集合的键获取流
-
根据Map集合的值获取流
-
根据Map集合的键值对对象获取流
/** * Created by PengZhiLin on 2021/8/9 11:43 */ public class Test { public static void main(String[] args) { // 创建Map集合,限制键的类型为Integer,值的类型为String Map<Integer,String> map = new HashMap<>(); // 添加键值对 map.put(1,"张三"); map.put(2,"李四"); map.put(3,"王五"); map.put(4,"赵六"); map.put(5,"田七"); //- 根据Map集合的键获取流 Stream<Integer> stream1 = map.keySet().stream(); //- 根据Map集合的值获取流 Stream<String> stream2 = map.values().stream(); //- 根据Map集合的键值对对象获取流 Stream<Map.Entry<Integer, String>> stream3 = map.entrySet().stream(); stream1.forEach(i-> System.out.println(i)); System.out.println("--------------"); stream2.forEach(str-> System.out.println(str)); System.out.println("--------------"); stream3.forEach(e-> System.out.println(e)); System.out.println("--------------"); } }
-
-
-
根据数组获取流---->使用Stream流的静态of方法
-
public static <T> Stream<T> of(T... values);
/** * Created by PengZhiLin on 2021/8/9 11:47 */ public class Test { public static void main(String[] args) { String[] names = {"张三","李四","王五"}; // 根据数组获取流 Stream<String> stream1 = Stream.of(names); stream1.forEach(name-> System.out.println(name)); // 直接传元素 Stream<String> stream2 = Stream.of("张三", "李四", "王五"); stream2.forEach(name-> System.out.println(name)); } }
-
3.4 Stream流常用方法
-
终结方法: 方法的返回值类型不是Stream流, 函数模型中一定要有终结方法,否则无法执行
-
延迟方法\非终结方法: 方法的返回值类型是Stream流
-
常用方法:
-
forEach: 逐一处理流中的元素
- `void forEach(Consumer action);
/** * Created by PengZhiLin on 2021/8/9 11:53 */ public class Test1_forEach { public static void main(String[] args) { // 获取流--->调用forEach方法 Stream<String> stream = Stream.of("张三", "李四", "王五"); // stream.forEach((String name)->{System.out.println(name);}); stream.forEach(name->System.out.println(name)); } }
-
count: 统计流中元素的个数
long count();
/** * Created by PengZhiLin on 2021/8/9 11:53 */ public class Test2_count { public static void main(String[] args) { // 获取流--->调用count方法 Stream<String> stream = Stream.of("张三", "李四", "王五"); long count = stream.count(); System.out.println("流中元素个数:" + count); } }
-
filter: 根据条件过滤
Stream<T> filter(Predicate<? super T> predicate);
/** * Created by PengZhiLin on 2021/8/9 12:02 */ public class Test3_filter { public static void main(String[] args) { // 获取流--调用filter方法 Stream<String> stream = Stream.of("张三", "张无忌", "李四", "王五"); // 过滤出姓张的元素,并打印输出 stream.filter((String name)->{return name.startsWith("张");}).forEach(name-> System.out.println(name)); } }
-
limit: 取流中前几个元素
-
Stream<T> limit(long maxSize);
-
注意:
1.参数一般开发中,设置大于0小于流中元素的个数
2.如果参数设置小于0的数,就会报异常
3.如果参数设置为0,返回的流中没有元素
4.如果参数设置大于流中元素个数,返回的流中就包含了流中所有的元素 -
案例:
/** * Created by PengZhiLin on 2021/8/9 12:07 */ public class Test4_limit { public static void main(String[] args) { /* `Stream<T> limit(long maxSize);`取流中前几个元素 1.参数一般开发中,设置大于0小于流中元素的个数 2.如果参数设置小于0的数,就会报异常 3.如果参数设置为0,返回的流中没有元素 4.如果参数设置大于流中元素个数,返回的流中就包含了流中所有的元素 */ // 获取流----调用limit方法 Stream<String> stream = Stream.of("张三", "李四", "王五","赵六","田七","王八"); stream.limit(16).forEach(name-> System.out.println(name)); } }
-
-
skip: 跳过流中前几个元素
-
Stream<T> skip(long n);跳过流中前几个元素
-
注意:
1.参数一般设置大于0或者小于元素个数
2.如果参数设置为0,返回新的流中包含了所有元素
3.如果参数设置为大于或者等于元素个数,返回新的流中没有元素
4.如果参数设置为小于0,报异常 -
案例:
/** * Created by PengZhiLin on 2021/8/9 12:12 */ public class Test5_skip { public static void main(String[] args) { /* - `Stream<T> skip(long n);跳过流中前几个元素` - 注意: 1.参数一般设置大于0或者小于元素个数 2.如果参数设置为0,返回新的流中包含了所有元素 3.如果参数设置为大于或者等于元素个数,返回新的流中没有元素 4.如果参数设置为小于0,报异常 */ // 获取流----调用limit方法 Stream<String> stream = Stream.of("张三", "李四", "王五","赵六","田七","王八"); stream.skip(3).forEach(name-> System.out.println(name)); } }
-
-
map: 映射\转换
<R> Stream<R> map(Function<? super T, ? extends R> mapper) 把流中T类型的元素转换为R类型;
/** * Created by PengZhiLin on 2021/8/9 12:16 */ public class Test6_map { public static void main(String[] args) { /* `<R> Stream<R> map(Function<? super T, ? extends R> mapper) 把流中T类型的元素转换为R类型;` */ // 获取流--->调用map方法 Stream<String> stream = Stream.of("110", "120", "119", "114"); // 把stream流中的元素转换为Integer类型,然后+1打印输出 stream.map((String str)->{ return Integer.parseInt(str);}).forEach(i-> System.out.println(i+1)); } }
-
concat: 拼接2个流
-
`static Stream concat(Stream<? extends T> a,Stream<? extends T> b);
/** * Created by PengZhiLin on 2021/8/9 12:22 */ public class Test7_concat { public static void main(String[] args) { // 获取流 Stream<String> stream1 = Stream.of("110", "120", "119", "114"); Stream<String> stream2 = Stream.of("张三", "李四", "王五","赵六","田七","王八"); // 调用concat方法 Stream.concat(stream1,stream2).forEach(str-> System.out.println(str)); } }
-
-
3.5 Stream综合案例
需求
现在有两个ArrayList
集合存储队伍当中的多个成员姓名,要求使用Stream流,依次进行以下若干操作步骤:
- 第一个队伍只要名字为3个字的成员姓名;
- 第一个队伍筛选之后只要前3个人;
- 第二个队伍只要姓张的成员姓名;
- 第二个队伍筛选之后不要前2个人;
- 将两个队伍合并为一个队伍;
- 根据姓名创建
Person
对象; - 打印整个队伍的Person对象信息。
两个队伍(集合)的代码如下:
public class DemoArrayListNames {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
// ....
}
}
实现
/**
* Created by PengZhiLin on 2021/8/9 14:30
*/
public class Test {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("张无忌");
one.add("孙子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
//1. 第一个队伍只要名字为3个字的成员姓名;
//2. 第一个队伍筛选之后只要前3个人;
Stream<String> stream1 = one.stream().filter(name -> name.length() == 3).limit(3);
//3. 第二个队伍只要姓张的成员姓名;
//4. 第二个队伍筛选之后不要前2个人;
Stream<String> stream2 = two.stream().filter(name -> name.startsWith("张")).skip(2);
//5. 将两个队伍合并为一个队伍;
//6. 根据姓名创建`Person`对象;
//7. 打印整个队伍的Person对象信息。
Stream.concat(stream1,stream2).map(name->new Person(name)).forEach(p->System.out.println(p));
}
}
3.6 收集Stream结果
收集到数组中
-
public Object[] toArray(); 把流中的元素收集到数组中
/** * Created by PengZhiLin on 2021/8/9 14:45 */ public class Test1_数组 { public static void main(String[] args) { // 获取流 Stream<String> stream = Stream.of("张三丰", "张无忌", "张翠山", "郭靖", "杨过"); // 操作流: 过滤出姓张的元素,并收集到数组中 Object[] arr = stream.filter(name -> name.startsWith("张")).toArray(); // 打印数组 System.out.println(Arrays.toString(arr)); } }
收集到集合中
-
<R, A> R collect(Collector<? super T, A, R> collector);把流中的元素收集到集合中
-
R: 返回值类型,也就是说R是什么类型,就返回什么类型的集合
-
参数Collector里面的泛型R确定了该方法的返回值类型
-
如何得到Collector呢???-----> 工具类Collectors
public static <T> Collector<T, ?, List<T>> toList()
public static <T> Collector<T, ?, Set<T>> toSet()
- eg;
stream.collect(Collectors.toList()) 收集到List集合
- eg: ``stream.collect(Collectors.toSet()) 收集到Set集合`
/** * Created by PengZhiLin on 2021/8/9 14:55 */ public class Test2_收集到List集合 { public static void main(String[] args) { // 获取流 Stream<String> stream = Stream.of("张三丰", "张无忌", "张翠山", "郭靖", "杨过"); // 操作流: 过滤出姓张的元素,并收集到List集合中 List<String> list = stream.filter(name -> name.startsWith("张")).collect(Collectors.toList()); System.out.println(list); } }
/** * Created by PengZhiLin on 2021/8/9 14:55 */ public class Test3_收集到Set集合 { public static void main(String[] args) { // 获取流 Stream<String> stream = Stream.of("张三丰", "张无忌", "张翠山", "郭靖", "杨过"); // 操作流: 过滤出姓张的元素,并收集到Set集合中 Set<String> set = stream.filter(name -> name.startsWith("张")).collect(Collectors.toSet()); System.out.println(set); } }
-
总结
必须练习:
1.线程6种状态之间的相互切换----->画图
2.等待唤醒机制--->如何实现等待唤醒机制,如何分析等待唤醒机制案例的执行流程
2.1 有规律的打印i循环和j循环
2.2 有规律的生产和吃包子
3.Lambda表达式:
3.1 默写使用Lambda表达式的套路---->4步
3.2 默写使用前提
3.3 默写省略规则
4.Stream流:
综合案例---->把结果收集到数组或者集合中
- 能够说出线程6个状态的名称
新建,可运行,锁阻塞,无限等待,计时等待,被终止
- 能够理解等待唤醒案例
如何实现等待唤醒机制:
- 1.使用锁对象调用wait()方法进入无限等待
- 2.使用锁对象调用notify()方法唤醒线程
- 3.调用wait(),notify()方法的锁对象要一致
分析等待唤醒机制程序的执行
- 1.不管是否使用等待唤醒机制,线程的调度都是抢占式
- 2.线程进入无限等待,线程就会释放锁,cpu,也不会再去争夺
- 3.唤醒其他线程,当前唤醒线程是不会释放锁,cpu的
- 4.无限等待线程被唤醒,拿到锁对象后,会从进入无限等待的位置继续往下执行
- 能够掌握Lambda表达式的标准格式与省略格式
Lambda使用套路
- 1.判断该位置上是否可以使用Lambda表达式--->使用前提
- 2.如果可以使用,直接写上()->{}
- 3.填充小括号中的内容--->函数式接口中抽象方法的形参一致
- 4.填充大括号中的内容--->重写函数式接口抽象方法需要的方法体
省略规则
- 小括号中参数类型可以省略不写
- 小括号中只有一个参数,那么小括号也可以省略
- 大括号中如果只有一条语句,那么大括号,return关键字,分号也可以省略(三个要一起省略)
使用前提: 函数式接口
- 能够通过集合、映射或数组方式获取流
使用Collection的stream方法
使用Stream流的of方法
- 能够掌握常用的流操作
forEach,count,filter,limit,skip,concat,map
- 能够将流中的内容收集到集合和数组中
Object[] toArray();
stream.collect(Collectors.toList()) 收集到List集合
stream.collect(Collectors.toSet()) 收集到Set集合
扩展多条线程实现等待唤醒
共享的旗帜变量 static int num = 1;
共享的锁对象
线程1:
如果num!=1,线程1就进入无限等待
如果num=1,线程1就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为2
线程2:
如果num!=2,线程2就进入无限等待
如果num=2,线程2就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为3
线程3:
如果num!=3,线程3就进入无限等待
如果num=3,线程3就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为1
/**
* Created by PengZhiLin on 2021/8/9 15:20
*/
public class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (Test.lock) {
//如果num!=1,线程1就进入无限等待
while (Test.num != 1) {
try {
Test.lock.wait();// 无限
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果num=1,线程1就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为2
if (Test.num == 1) {
System.out.println("黑马程序员--1");
// 唤醒其他所有无限等待线程
Test.lock.notifyAll();
// 修改旗帜变量的值
Test.num = 2;
}
}
}
}
}
/**
* Created by PengZhiLin on 2021/8/9 15:20
*/
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (Test.lock) {
//如果num!=2,线程2就进入无限等待
while (Test.num != 2) {
try {
Test.lock.wait();// 无限
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果num=2,线程2就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为3
if (Test.num == 2) {
System.out.println("传智播客--2");
// 唤醒其他所有无限等待线程
Test.lock.notifyAll();
// 修改旗帜变量的值
Test.num = 3;
}
}
}
}
}
/**
* Created by PengZhiLin on 2021/8/9 15:20
*/
public class MyThread3 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (Test.lock) {
// 如果num!=3,线程3就进入无限等待
while (Test.num != 3) {
try {
Test.lock.wait();// 无限
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果num=3,线程3就执行任务代码,唤醒其他所有无限等待线程,修改旗帜变量num的值为1
if (Test.num == 3) {
System.out.println("javaee--3---i:" + i);
// 唤醒其他所有无限等待线程
Test.lock.notifyAll();
// 修改旗帜变量的值
Test.num = 1;
}
}
}
}
}
/**
* Created by PengZhiLin on 2021/8/9 15:18
*/
public class Test {
//共享的旗帜变量
static int num = 1;
//共享的锁对象
static Object lock = new Object();
public static void main(String[] args) {
// 创建并启动三条线程
new MyThread1().start();
new MyThread2().start();
new MyThread3().start();
}
}