day09【线程状态、等待与唤醒、Lambda表达式、Stream流】
今日内容
- 多线程
- 线程的状态------>必须掌握
- 等待唤醒机制------>必须掌握
- 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)
让当前线程进入到睡眠状态,到毫秒后自动醒来继续执行
public class Test {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
System.out.println("i的值:"+i);
// 打印一次暂停3秒
Thread.sleep(3000);
}
}
}
- 无限等待
- Object类的方法:
-
public void wait()
: 让当前线程进入到等待状态 此方法必须锁对象调用.
public void notify()
: 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.
public void notifyAll()
: 唤醒当前锁对象上所有等待状态的线程 此方法必须锁对象调用.- 案例1: 无限等待线程
public class Test1 {
// 锁对象
public static Object obj = new Object();
public static void main(String[] args) {
// 创建并启动线程--->进入无限等待
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("无限等待线程: 准备进入无限等待...");
synchronized (obj) {
try {
obj.wait();// 进入无限等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("无限等待线程:被其他线程唤醒,并获取到锁对象,继续执行...");
}
}
}).start();
}
}
- 案例2: 等待和唤醒案例
public class Test2 {
// 锁对象
public static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
// 创建并启动线程--->进入无限等待
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("无限等待线程: 准备进入无限等待...");
synchronized (obj) {// 加锁
try {
obj.wait();// 进入无限等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("无限等待线程:被其他线程唤醒,并获取到锁对象,继续执行...");
}// 释放锁
}
}).start();
// 为了让无限等待线程先执行,开启唤醒线程之前睡眠一下
Thread.sleep(100);
// 创建并启动线程--->唤醒等待线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("唤醒线程:准备唤醒无限等待线程");
synchronized (obj){
obj.notify();
}
}
}).start();
/*
分析程序:
1.程序进入无限等待状态会释放锁,释放cpu,并且不会再去抢占
2.无限等待线程被唤醒,拿到锁对象之后,会从进入无限等待的位置继续往下执行
*/
}
}
1.3 等待唤醒机制
什么是等待唤醒机制
- 概述: 使用等待和唤醒实现多条线程之间有规律的执行
- 例如: 子线程打印i循环,主线程打印j循环
- 不使用等待唤醒机制: 结果是主线程和子线程随机交替打印输出----->没有规律
- 使用等待唤醒机制: 结果就要有规律的打印输出
- 打印1次i循环,然后打印1次j循环…依次循环打印输出…---->有规律
- 如何实现:
- 子线程打印1次i循环,然后唤醒主线程来执行, 就进入无限等待
- 主线程打印1次j循环,然后唤醒子线程来执行,就进入无限等待
- 子线程打印1次i循环,然后唤醒主线程来执行,就进入无限等待
- 主线程打印1次j循环,然后唤醒子线程来执行,就进入无限等待
- …
如何实现等待唤醒机制:
- 1.使用锁对象调用wait()方法进入无限等待
- 2.使用锁对象调用notify()方法唤醒线程
- 3.调用wait(),notify()方法的锁对象要一致
- 案例: 主线程和子线程有规律的交替打印输出
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (Test.lock) {
if (Test.flag == false){
// 无限等待
try {
Test.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (Test.flag == true) {
System.out.println("子线程i的值是:" + i);
Test.lock.notify();
Test.flag = false;
}
}
}
}
}
public class Test {
// 锁对象
public static Object lock = new Object();
// 开关变量---旗帜变量
public static boolean flag = false;// true: 子线程执行 false: 主线程执行
public static void main(String[] args) {
// 需求: 主线程和子线程有规律的交替打印输出
// 创建并启动子线程
new MyThread().start();
// 主线程的任务
for (int j = 0; j < 100; j++) {
synchronized (lock) {
if (flag == true){
// 无限等待
try {
Test.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (flag == false) {
System.out.println("主线程j的值是:" + j);
lock.notify();
flag = true;
}
}
}
// 结果:
// j-i-j-i-j-i-j....
}
}
分析等待唤醒机制程序的执行
- 1.不管是否使用等待唤醒机制,线程的调度都是抢占式
- 2.线程进入无限等待,线程就会释放锁,cpu,也不会再去争夺
- 3.唤醒其他线程,当前唤醒线程是不会释放锁,cpu的
- 4.无限等待线程被唤醒,拿到锁对象后,会从进入无限等待的位置继续往下执行
1.4 等待唤醒案例
需求
- 等待唤醒机制其实就是经典的“生产者与消费者”的问题。
- 就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:
分析
包子铺线程生产包子,生产完了,包子就有了,唤醒吃货线程来吃包子,然后包子铺线程进入无限等待;
吃货线程吃包子,吃完了,包子就没有了,唤醒包子铺线程来生产包子,然后吃货线程进入无限等待;
包子铺线程生产包子,生产完了,包子就有了,唤醒吃货线程来吃包子,然后包子铺线程进入无限等待;
吃货线程吃包子,吃完了,包子就没有了,唤醒包子铺线程来生产包子,然后吃货线程进入无限等待;
.....
包子类:
状态--->false:没有包子,ture:有包子
馅儿
包子铺线程:
包子有了,进入无限等待
包子没有了,执行代码(生产包子),唤醒其他线程,修改旗帜变量
吃货线程:
包子没有了,进入无限等待
包子有了,执行代码(吃包子),唤醒其他线程,修改旗帜变量
实现
- 包子类
public class BaoZi {
public String xian;
public boolean flag;// 默认值为false
}
- 包子铺线程
public class BaoZiPu extends Thread {
BaoZi bz;
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
while (true) {
synchronized (bz) {
// 包子有了,进入无限等待
if (bz.flag == true){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 包子没有了,执行代码(生产包子),唤醒其他线程,修改旗帜变量
if (bz.flag == false){
System.out.println("包子铺线程:开始做包子...");
bz.xian = "韭菜馅儿";
bz.flag = true;
bz.notify();
System.out.println("包子铺线程:包子做好了,吃货快来吃包子...");
}
}
}
}
}
- 吃货线程
public class ChiHuo extends Thread {
BaoZi bz;
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
@Override
public void run() {
while (true) {
synchronized (bz) {
// 包子没有了,进入无限等待
if (bz.flag == false){
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 包子有了,执行代码(吃包子),唤醒其他线程,修改旗帜变量
if (bz.flag == true){
System.out.println("吃货线程:开始吃包子,包子的馅儿:"+bz.xian);
bz.flag = false;
bz.notify();
System.out.println("吃货线程:包子吃完了,包子铺快来做包子========");
}
}
}
}
}
- 测试类
public class Test {
public static void main(String[] args) {
// 创建包子对象
BaoZi bz = new BaoZi();// xian: null,flag: false
// 创建并启动包子铺线程
new BaoZiPu(bz).start();
// 创建并启动吃货线程
new ChiHuo(bz).start();
}
}
第二章 Lambda表达式
2.1 函数式编程思想概述
面向对象编程思想
面向对象强调的是对象 , “必须通过对象的形式来做事情”,相对来讲比较复杂,有时候我们只是为了做某件事情而不得不创建一个对象 , 例如线程执行任务,我们不得不创建一个实现Runnable接口对象,但我们真正希望的是将run方法中的代码传递给线程对象执行
函数编程思想
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。例如线程执行任务 , 使用函数式思想 , 我们就可以通过传递一段代码给线程对象执行,而不需要创建任务对象
2.2 Lambda表达式的体验
- 实现Runnable接口的方式创建线程执行任务
- 匿名内部类方式创建线程执行任务
- 以上2种方式,其实都是通过Runnable的实现类对象来传递任务给线程执行
- 思考: 是否能不通过实现类对象传递任务给线程执行呢?—>函数式编程
- Lambda方式创建线程执行任务
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("实现类的方式传递任务");
}
}
public class Test {
public static void main(String[] args) {
// 实现Runnable接口的方式创建线程执行任务
MyRunnable mr = new MyRunnable();
new Thread(mr).start();
//匿名内部类方式创建线程执行任务
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类的方式传递任务");
}
}).start();
//以上2种方式,其实都是通过Runnable的实现类对象来传递任务给线程执行
// 思考: 是否能不通过实现类对象传递任务给线程执行呢?--->函数式编程
// 函数式编程: 强调是做什么,而不是以什么形式做
//Lambda方式创建线程执行任务
new Thread(()->{System.out.println("函数式编程思想方式传递任务");}).start();
}
}
- Lambda表达式没有特殊功能,就是用来简化代码的
2.3 Lambda表达式的标准格式
- 标准格式
- 格式:
(参数类型 参数名,参数类型 参数名,...)->{ 代码块 }
- Lambda的使用前提条件:
- 接口中只有一个抽象方法的接口,才可以使用Lambda表达式
- 只有一个抽象方法的接口叫做函数式接口,函数式接口可以使用@FunctionalInterface注解标识
- eg:
// A接口就是函数式接口,可以使用Lambda表达式替换实现类对象
public interface A{
void method1();
}
// B接口就是函数式接口,可以使用Lambda表达式替换实现类对象
@FunctionalInterface
public interface B{
void method1();
}
// C接口就不是一个函数式接口,就不能使用Lambda表达式
public interface C{
void method1();
void method2();
}
// D接口就是一个函数式接口,可以使用Lambda表达式替换实现类对象
public interface D{
void method1();
default void method2(){
}
...
}
- 格式说明
- 小括号中的参数类型,参数个数,参数顺序要和函数式接口中抽象方法的形参列表一致
- -> 固定格式,表示指向
- 大括号中的内容其实就是之前重写接口中抽象方法的方法体
- 案例演示
- Runnable函数式接口
public class Test {
public static void main(String[] args) {
// - Runnable函数式接口
new Thread(() -> {
System.out.println("任务代码");
System.out.println("任务代码");
System.out.println("任务代码");
System.out.println("任务代码");
}).start();
}
}
- Comparator函数式接口
public class Test {
public static void main(String[] args) {
// - Comparator函数式接口
ArrayList<Integer> list = new ArrayList<>();
list.add(500);
list.add(100);
list.add(400);
list.add(200);
list.add(300);
// 排序--->升序排序
Collections.sort(list, (Integer i1, Integer i2) -> {
return i1 - i2;
});
System.out.println("排序后的集合:" + list);
// 排序--->降序排序
Collections.sort(list,(Integer o1,Integer o2)->{
return o2 - o1;
});
System.out.println("排序后的集合:" + list);
}
}
- Lambda使用套路
- 1.判断该位置上是否可以使用Lambda表达式—>使用前提
- 2.如果可以使用,直接写上()->{}
- 3.填充小括号中的内容—>函数式接口中抽象方法的形参一致
- 4.填充大括号中的内容—>重写函数式接口抽象方法需要的方法体
2.4 Lambda表达式省略格式
- 省略规则
- 小括号中参数类型可以省略不写
- 小括号中只有一个参数,那么小括号也可以省略
- 大括号中如果只有一条语句,那么大括号,return关键字,分号也可以省略(三个要一起省略)
- 案例演示
public class Test {
public static void main(String[] args) {
// - Runnable函数式接口
// 标准格式
new Thread(() -> {
System.out.println("任务代码1");
}).start();
// 省略格式
new Thread(() -> System.out.println("任务代码2")).start();
// - Comparator函数式接口
ArrayList<Integer> list = new ArrayList<>();
list.add(500);
list.add(100);
list.add(400);
list.add(200);
list.add(300);
// 排序--->升序排序
// 标准格式:
/*Collections.sort(list, (Integer i1, Integer i2) -> {
return i1 - i2;
});*/
// 省略格式:
Collections.sort(list, ( i1, i2) -> i1 - i2);
System.out.println("排序后的集合:" + list);
// 排序--->降序排序
// 省略格式:
Collections.sort(list,( o1, o2)-> o2 - o1);
System.out.println("排序后的集合:" + list);
}
}
2.5 Lambda的表现形式
- Lambda的表现形式: Lambda表达式会出现在哪些位置
- 变量形式: 赋值一个Lambda表达式
public class Test {
public static void main(String[] args) {
// - 变量形式:
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Runnable匿名内部类--实现类对象");
}
};
Runnable r2 = () -> {
System.out.println("Runnable对应的Lambda表达式");
};
}
}
- 参数形式: 传入Lambda表达式作为实参
public class Test {
public static void main(String[] args) {
Runnable r2 = () -> {
System.out.println("Runnable对应的Lambda表达式");
};
// - 参数形式:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Runnable匿名内部类--实现类对象");
}
}).start();
new Thread(() -> {
System.out.println("Runnable对应的Lambda表达式");
}).start();
new Thread(r2).start();
}
}
- 返回值形式: 返回一个Lambda表达式(返回值)
public class Test {
public static void main(String[] args) {
// - 返回值形式:
// - Comparator函数式接口
ArrayList<Integer> list = new ArrayList<>();
list.add(500);
list.add(100);
list.add(400);
list.add(200);
list.add(300);
Comparator<Integer> comp = getComparator();
Collections.sort(list, comp);
System.out.println("排序后:" + list);
}
public static Comparator<Integer> getComparator() {
/*return new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
};*/
//return (Integer o1, Integer o2)->{return o2 - o1;};// 标准格式
return ( o1, o2)-> o2 - o1;// 省略格式
}
}
第三章 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流操作集合
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("洪七公");
// 需求:
// 1. 队伍中只要名字为3个字的成员姓名;
// 1.1 创建一个新的集合,用来存储名字为3个字的姓名
ArrayList<String> list1 = new ArrayList<>();
// 1.2 循环遍历one这个集合
for (String name : one) {
// 1.3 在循环中,判断筛选,存储
if (name.length() == 3){
list1.add(name);
}
}
// 2. 队伍中筛选之后只要前3个人;
// 2.1 创建一个新的集合,用来存储前3个
ArrayList<String> list2 = new ArrayList<>();
// 2.2 循环遍历3次,存储
for (int i = 0; i < 3; i++) {
String e = list1.get(i);
list2.add(e);
}
System.out.println("list2:"+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获取流
public class Test1_根据Collection集合获取流 {
public static void main(String[] args) {
// List
List<String> list = new ArrayList<>();
list.add("王宝强");
list.add("贾乃亮");
list.add("谢霆锋");
list.add("陈羽凡");
// 获取流
Stream<String> stream1 = list.stream();
// Set
Set<String> set = new HashSet<>();
set.add("马蓉");
set.add("李小璐");
set.add("张柏芝");
set.add("白百何");
// 获取流
Stream<String> stream2 = set.stream();
}
}
- 根据Map获取流
- 根据Map集合的键获取流
- 根据Map集合的值获取流
- 根据Map集合的键值对对象获取流
public class Test2_根据Map集合获取流 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("王宝强","马蓉");
map.put("贾乃亮","李小璐");
map.put("谢霆锋","张柏芝");
map.put("陈羽凡","白百何");
// - 根据Map集合的键获取流
Stream<String> stream1 = map.keySet().stream();
//- 根据Map集合的值获取流
Stream<String> stream2 = map.values().stream();
//- 根据Map集合的键值对对象获取流
Stream<Map.Entry<String, String>> stream3 = map.entrySet().stream();
}
}
- 根据数组获取流---->使用Stream流的静态of方法
public static <T> Stream<T> of(T... values);
public class Test3_根据数组获取流 {
public static void main(String[] args) {
String[] arr = {"王宝强",
"贾乃亮",
"谢霆锋",
"陈羽凡"};
// 根据数组元素获取流
Stream<String> stream1 = Stream.of(arr);
// 直接传值获取流
Stream<String> stream2 = Stream.of("王宝强", "贾乃亮", "谢霆锋", "陈羽凡");
}
}
3.4 Stream流常用方法
- 终结方法: 方法的返回值类型不是Stream流,流中一定要有终结方法,否则无法执行
- 延迟方法: 方法的返回值类型是Stream流
- 常用方法:
- forEach: 逐一处理流中的元素
public class Test1_forEach {
public static void main(String[] args) {
/*
forEach:
void forEach(Consumer<? super T> action);逐一处理流中的元素
参数Consumer: 函数式接口
*/
// 获取流
Stream<String> stream = Stream.of("王宝强", "贾乃亮", "谢霆锋", "陈羽凡");
// 需求:分别输出流中的所有元素
//stream.forEach((String t)->{System.out.println(t);});// 标准格式
stream.forEach(t -> System.out.println(t));// 省略格式
}
}
- count: 统计流中元素的个数
public class Test2_count {
public static void main(String[] args) {
/*
forEach:
long count();统计流中元素的个数
*/
// 获取流
Stream<String> stream = Stream.of("王宝强", "贾乃亮", "谢霆锋", "陈羽凡");
// 需求:分别输出流中的所有元素
long count = stream.count();
System.out.println("stream流元素的个数:"+count);
}
}
- filter: 根据条件过滤
public class Test3_filter {
public static void main(String[] args) {
/*
filter:
Stream<T> filter(Predicate<? super T> predicate); 根据指定条件过滤,满足条件的元素就组成一个新的流,并返回新的Stream流
参数Predicate: 函数式接口---->用来做判断
*/
// 获取流
Stream<String> stream = Stream.of("王宝强", "贾乃亮", "王叔叔","隔壁老王", "谢霆锋", "王小二", "陈羽凡");
// 需求:过滤出姓王的元素,并打印输出
stream.filter((String t)->{return t.startsWith("王");}).forEach(e->System.out.println(e));
}
}
- limit: 取流中前几个元素
public class Test4_limit {
public static void main(String[] args) {
/*
limit:
Stream<T> limit(long maxSize); 取前几个元素
注意:
1.参数一般开发中,设置大于0小于流中元素的个数
2.如果参数设置小于0的数,就会报异常
3.如果参数设置为0,返回的流中没有元素
4.如果参数设置大于流中元素个数,返回的流中就包含了流中所有的元素
*/
// 获取流
Stream<String> stream = Stream.of("王宝强", "贾乃亮", "王叔叔","隔壁老王", "谢霆锋", "王小二", "陈羽凡");
// 需求: 取前4个元素,打印输出
stream.limit(4).forEach(e-> System.out.println(e));
//stream.limit(9).forEach(e-> System.out.println(e));// 取流中所有元素
//stream.limit(-9).forEach(e-> System.out.println(e));// 报IllegalArgumentException异常
}
}
- skip: 跳过流中前几个元素
public class Test5_skip {
public static void main(String[] args) {
/*
skip:
Stream<T> skip(long n);跳过前几个元素
注意:
1.参数一般设置大于0或者小于元素个数
2.如果参数设置为0,返回新的流中包含了所有元素
3.如果参数设置为大于或者等于元素个数,返回新的流中没有元素
4.如果参数设置为小于0,报异常
*/
// 获取流
Stream<String> stream = Stream.of("王宝强", "贾乃亮", "王叔叔","隔壁老王", "谢霆锋", "王小二", "陈羽凡");
// 需求: 跳过前4个元素,剩余元素打印输出
stream.skip(4).forEach(e-> System.out.println(e));
//stream.skip(8).forEach(e-> System.out.println(e));// 没有元素输出
//stream.skip(-8).forEach(e-> System.out.println(e));// 报异常
}
}
- map: 映射\转换
public class Test6_map {
public static void main(String[] args) {
/*
map:
<R> Stream<R> map(Function<? super T, ? extends R> mapper); 将流中T类型的元素转换为R类型的元素,返回一个新的流(R)
注意:
1.T,R的类型可以一致,也可以不一致
2.参数Function: 函数式接口-->转换接口
*/
// 获取流
Stream<String> stream = Stream.of("100", "200", "300", "400", "500");
// 需求:把流中字符串类型转换为Integer类型,打印输出
stream.map((String t)->{return Integer.parseInt(t);}).forEach(e-> System.out.println(e+1));
}
}
- concat: 拼接2个流
public class Test7_concat {
public static void main(String[] args) {
/*
concat:
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) 拼接2个流中的元素,得到新的流
*/
// 获取流
Stream<String> stream1 = Stream.of("100", "200", "300", "400", "500");
// 获取流
Stream<String> stream2 = Stream.of("王宝强", "贾乃亮", "王叔叔","隔壁老王");
// 拼接
Stream.concat(stream1,stream2).forEach(e-> System.out.println(e));
}
}
- …
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("张三");
// ....
}
}
实现
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("洪七公");
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((String t) -> {
return t.length() == 3;
}).limit(3);
// 3. 第二个队伍只要姓张的成员姓名;
// 4. 第二个队伍筛选之后不要前2个人;
Stream<String> stream2 = two.stream().filter((String t) -> {
return t.startsWith("张");
}).skip(2);
// 5. 将两个队伍合并为一个队伍;
// 6. 根据姓名创建Person对象;
// 7. 打印整个队伍的Person对象信息。
Stream.concat(stream1,stream2).map((String name)->{ return new Person(name);}).forEach(p-> System.out.println(p));
}
}
3.6 收集Stream结果
收集到数组中
public statci Object[] toArray(); 把流中的元素收集到数组中
public class Test1_收集到数组中 {
public static void main(String[] args) {
// 获取流
Stream<String> stream = Stream.of("王宝强", "贾乃亮", "王叔叔","隔壁老王", "谢霆锋", "王小二", "陈羽凡");
// 需求:过滤出姓王的元素,把结果收集到数组中
Object[] arr = stream.filter(name -> name.startsWith("王")).toArray();
for (Object o : arr) {
System.out.println(o);
}
}
}
收集到集合中
<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集合`
public class Test1_收集到集合中 {
public static void main(String[] args) {
// 获取流
Stream<String> stream = Stream.of("王宝强", "贾乃亮", "王叔叔", "隔壁老王", "谢霆锋", "王小二", "陈羽凡");
// 需求:过滤出姓王的元素,把结果收集到集合中
//List<String> list = stream.filter(name -> name.startsWith("王")).collect(Collectors.toList());
//System.out.println("list:" + list);
Set<String> set = stream.filter(name -> name.startsWith("王")).collect(Collectors.toSet());
System.out.println("set:" + 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集合