【线程状态、等待与唤醒、Lambda表达式、Stream流】

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流,依次进行以下若干操作步骤:

  1. 第一个队伍只要名字为3个字的成员姓名;
  2. 第一个队伍筛选之后只要前3个人;
  3. 第二个队伍只要姓张的成员姓名;
  4. 第二个队伍筛选之后不要前2个人;
  5. 将两个队伍合并为一个队伍;
  6. 根据姓名创建Person对象;
  7. 打印整个队伍的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表达式的套路---->43.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集合

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值