Stream流

1 Stream流思想

Stream是专门用来处理容器数据的API,是一种以流水线作业的模式来处理数据的思想。

Stream流使用的过程中,含有三种类型的方法:

  1. 获取Stream流
    创建一条流水线,并把数据放到流水线上准备进行操作
  2. 中间方法
    流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。
  3. 终结方法
    一个Stream流只能有一个终结方法,是流水线上的最后一个操作

2 Stream的获取方式

  1. 单列集合

    可以使用Collection接口中的默认方法stream()生成流
    default Stream<E> stream()

  2. 双列集合

间接的生成流
可以先通过keySet或者entrySet获取一个Set集合,再获取Stream流

  1. 数组

    Arrays中的静态方法stream 生成流

3 Stream流中间方法

  1. Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤
  2. Predicate接口中的方法 : boolean test(T t):对给定的参数进行判断,返回一个布尔值
  3. Stream<T> limit(long n):获取前n个元素
  4. Stream<T> skip(long n):跳过n个数的数据
  5. static <T> Stream<T> concat(Stream a, Stream b):合并ab两个流为一个流
  6. Stream<T> distinct():去除流中重复的元素。依赖(hashCodeequals方法)
  7. Stream<T> sorted () : 将流中元素按照自然排序的规则排序
  8. Stream<T> sorted (Comparator<? super T> comparator) : 将流中元素按照自定义比较器规则排序
  9. <R> Stream<R> map(Function<? super T, ? extends R> mapper): 将流中数据映射为新的数据

使用参考代码:

  1. public class StreamDemo3 {
  2. public static void main(String[] args) {
  3. //ArrayList<String> list = new ArrayList<>(List.of("曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔"));
  4. ArrayList<String> list = new ArrayList<>();
  5. Collections.addAll(list, "曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔");
  6. /* list.stream().forEach( (String str)-> {
  7. System.out.println(str);
  8. } );*/
  9. //遍历流中数据 foreach
  10. list.stream().forEach(str -> System.out.println(str));
  11. System.out.println("=====");
  12. //筛选流中数据:filter Stream<T> filter(Predicate predicate)
  13. list.stream()
  14. .filter(str -> str.startsWith("曹"))
  15. .filter(str -> str.length() == 3)
  16. .forEach(str -> System.out.println(str));
  17. System.out.println("=====");
  18. // Stream<T> limit(long maxSize):截取指定参数个数的数据
  19. // Stream<T> skip(long n):跳过指定参数个数的数据
  20. Stream.of("A", "B", "C", "D", "E", "F")
  21. .limit(5) //取前5个
  22. .skip(2)//跳过前2个
  23. .forEach(str -> System.out.println(str));
  24. System.out.println("=====");
  25. // 4 static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
  26. Stream<String> s1 = Stream.of("A", "B", "C");
  27. Stream<String> s2 = Stream.of("X", "Y", "Z");
  28. Stream.concat(s1, s2).forEach(System.out::println);
  29. System.out.println("=====");
  30. // 5 Stream<T> distinct():去除流中重复的元素。依赖(hashCode和equals方法)
  31. Stream.of("A", "B", "C", "A", "B", "C")
  32. .distinct() //可以去重
  33. .forEach(System.out::println);
  34. // 6 Stream<T> sorted () : 将流中元素按照自然排序的规则排序
  35. System.out.println("=====");
  36. Stream.of("A", "C", "B", "E", "D")
  37. .sorted()
  38. .forEach(System.out::println);
  39. System.out.println("=====");
  40. // 7 Stream<T> sorted (Comparator<? super T> comparator) : 将流中元素按照自定义比较器规则排序
  41. Stream.of("A", "C", "B", "E", "D")
  42. //.sorted(Comparator.reverseOrder())
  43. .sorted((o1, o2) -> o2.compareTo(o1))
  44. .forEach(System.out::println);
  45. // 8 数据的转换 map
  46. // <R> Stream<R> map(Function<T, R> mapper);
  47. // Function函数式接口中存在抽象方法 :
  48. // R apply (T t) 将T (流中原有数据类型)类型转换为R(将要转换的类型)类型
  49. //需求: 将字符串 T 转换为学生类型 R
  50. Stream.of("张三-18", "李四-20")
  51. .map(str -> {
  52. String[] arr = str.split("-");
  53. return new Student(arr[0], Integer.parseInt(arr[1]));
  54. })
  55. .forEach(System.out::println);
  56. }
  57. }
  58. class Student {
  59. String name;
  60. int age;
  61. public Student(String name, int age) {
  62. this.name = name;
  63. this.age = age;
  64. }
  65. @Override
  66. public String toString() {
  67. return "Student{" +
  68. "name='" + name + '\'' +
  69. ", age=" + age +
  70. '}';
  71. }
  72. }

4 Stream流终结方法

  1. 1 void forEach(Consumer action):对此流的每个元素执行操作
  2. Consumer接口中的方法 void accept(T t):对给定的参数执行此操作
  3. 2 long count():返回此流中的元素个数
  4. 3 流收集方法,看下一个知识点

使用参考代码:

  1. public class StreamDemo4 {
  2. public static void main(String[] args) {
  3. //ArrayList<String> list = new ArrayList<>(List.of("曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔"));
  4. ArrayList<String> list = new ArrayList<>();
  5. Collections.addAll(list, "曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔");
  6. //集合本身也有一个forEach方法用来做遍历
  7. //list.forEach(str-> System.out.println(str));
  8. // 1 void forEach(Consumer action):对此流的每个元素执行操作
  9. Stream<String> s1 = list.stream();
  10. s1.forEach(str-> System.out.println(str));
  11. // 2 long count():返回此流中的元素个数
  12. //流只能用一次,
  13. long count = list
  14. .stream()
  15. .limit(10)
  16. .skip(5)
  17. .count();
  18. System.out.println("count = " + count);
  19. }
  20. }
  21. //输出结果
  22. 曹操
  23. 曹孟德
  24. 曹阿瞒
  25. 曹阿瞒
  26. 曹阿瞒
  27. 刘备
  28. 刘玄德
  29. 刘皇叔
  30. count = 3

5 流收集方法

Stream流在操作的过程中,不会导致原集合或者数组内容发生改变。如果想要流系列操作后的结果,我们需要进行收集。

  1. R collect(Collector collector) : 此方法只负责收集流中的数据 , 创建集合添加数据动作需要依赖于参数
  2. 工具类Collectors提供了具体的收集方式
  3. public static <T> Collector toList():把元素收集到List集合中
  4. public static <T> Collector toSet():把元素收集到Set集合中
  5. public static Collector toMap(Function keyMapper,Function valueMapper):把元素收集到Map集合中

收集到List集合,Set集合参考

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.Set;
  5. import java.util.stream.Collector;
  6. import java.util.stream.Collectors;
  7. public class StreamDemo6 {
  8. public static void main(String[] args) {
  9. //ArrayList<String> list = new ArrayList<>(List.of("曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔")); //JDK9之上支持
  10. ArrayList<String> list = new ArrayList<>();
  11. Collections.addAll(list, "曹操", "曹孟德", "曹阿瞒", "曹阿瞒", "刘备", "刘玄德", "刘皇叔");
  12. // 使用stream流 , 过滤获取姓 曹 且 三个字的名字
  13. // collect负责收集元素 , 不负责创建List集合对象
  14. // 如果需要创建List集合对象需要依赖于Collectors.toList()
  15. List<String> caoList = list.stream()
  16. .filter(name -> name.startsWith("曹") && name.length() == 3)
  17. .collect(Collectors.toList());
  18. System.out.println("caoList = " + caoList);
  19. //[曹孟德, 曹阿瞒, 曹阿瞒]
  20. System.out.println("===============");
  21. // public static <T> Collector toSet():把元素收集到Set集合中
  22. Set<String> caoSet = list.stream()
  23. .filter(name -> name.startsWith("曹") && name.length() == 3)
  24. .collect(Collectors.toSet());
  25. System.out.println("caoSet = " + caoSet);
  26. //[曹阿瞒, 曹孟德]
  27. }
  28. }

收集到Map集合参考

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.Map;
  4. import java.util.stream.Collectors;
  5. /*
  6. 1 创建一个ArrayList集合,并添加以下字符串。字符串中前面是姓名,后面是年龄
  7. "zhangsan,23"
  8. "lisi,24"
  9. "wangwu,25"
  10. 2 保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值
  11. 需求1 : 保留年龄大于等于24岁的人
  12. 需求2 : 并将结果收集到Map集合中,姓名为键,年龄为值
  13. 收集方法 :
  14. Collectors工具类的
  15. public static Collector toMap(Function keyMapper , Function valueMapper):把元素收集到Map集合中
  16. */
  17. public class StreamDemo7 {
  18. public static void main(String[] args) {
  19. ArrayList<String> list = new ArrayList<>();
  20. list.add("zhangsan,23");
  21. list.add("lisi,24");
  22. list.add("wangwu,25");
  23. // 需求1 : 保留年龄大于等于24岁的人
  24. Map<String, String> map = list.stream()
  25. .filter(str -> Integer.parseInt(str.split(",")[1]) >= 24)
  26. //.forEach(System.out::println);
  27. .collect(Collectors.toMap(
  28. //键的Function
  29. str -> str.split(",")[0]
  30. ,
  31. //值的Function
  32. str -> str.split(",")[1]
  33. ));
  34. System.out.println("map = " + map);
  35. }
  36. }

多线程

1 名词概念

  1. 并行:在同一时刻,有多个任务在多个CPU上同时执行。
  2. 并发:在同一时刻,有多个任务在单个CPU上交替执行。
  3. 进程:进程简单地说就是在多任务操作系统中,每个独立执行的程序,所以进程也就是“正在进行的程序”。(Windows系统中,我们可以在任务管理器中看到进程)
  4. 线程:线程是程序运行的基本执行单元。当操作系统执行一个程序时,会在系统中建立一个进程,该进程必须至少建立一个线程(这个线程被称为主线程)作为这个程序运行的入口点。因此,在操作系统中运行的任何程序都至少有一个线程。
  5. 多线程:
    1. 硬件角度:现代CPU能够同时处理多个线程任务
    2. 软件角度:一个进程,可以同时开启多个线程执行不同任务
      image.png

可以提高系统资源的利用率,及解决问题的效率。

2 线程的创建方式

java.lang.Thread 是线程类,可以用来给进程创建线程处理任务使用。要使用线程先介绍两个比较重要的方法:
- publicvoid run() : 线程执行任务的方法,是线程启动后第一个执行的方法
- publicvoid start() : 启动线程的方法,线程对象调用该方法后,Java虚拟机就会调用此线程的run方法。

线程的创建方式1:继承Thread方式

基本步骤:

  1. 创建一个类继承Thread类。
  2. 在类中重写run方法(线程执行的任务放在这里)
  3. 创建线程对象,调用线程的start方法开启线程。

代码参考:
需求 :
我们启动一个Java程序,其实默认就存在一个主线程(main方法所在线程)
接下来,我们在主线程启动一个线程,打印1到100的数字,主线程启动完线程后又打印1到100的数字。
此时主线程和启动的线程在并发执行,观察控制台打印的结果。

  1. public class MyThread01 {
  2. public static void main(String[] args) {
  3. // 3 创建线程对象,调用线程的start方法开启线程。
  4. Thread01 t1 = new Thread01();
  5. t1.start();
  6. //当主线程开启了t1线程后,不会等待线程t1执行完
  7. //继续执行后续代码
  8. for (int i = 0; i < 100; i++) {
  9. System.out.println("旺财:" + i);
  10. }
  11. }
  12. }
  13. //1 创建一个类继承Thread类。
  14. class Thread01 extends Thread {
  15. //2 在类中重写run方法(线程执行的任务放在这里)
  16. @Override
  17. public void run() {
  18. for (int i = 0; i < 100; i++) {
  19. System.out.println("小强:" + i);
  20. }
  21. }
  22. }

线程的创建方式2 : 实现Runable方式

实现步骤如下:

  1. 定义任务类实现Runnable,并重写run方法
  2. 创建任务对象
  3. 使用含有Runnable参数的构造方法,创建线程对象并指定任务。
  4. 调用线程的start方法,开启线程。

代码参考
需求 :
我们启动一个Java程序,其实默认就存在一个主线程(main方法所在线程)
接下来,我们在主线程启动一个线程,打印1到100的数字,主线程启动完线程后又打印1到100的数字。
此时主线程和启动的线程在并发执行,观察控制台打印的结果。

  1. public class MyThread02 {
  2. public static void main(String[] args) {
  3. //2 创建任务对象
  4. MyTask m1 = new MyTask();
  5. // 3 创建Thread类型的对象 , Thread类的构造方法需要接受一个Runnable实现类对象
  6. //public Thread(Runnable target) : 接受一个Runnable接口的实现类对象
  7. Thread t1 = new Thread(m1);
  8. //4 调用线程的start方法,开启线程
  9. t1.start();
  10. //使用Lambda直接实现Runnable方式
  11. Thread t2 = new Thread(()-> System.out.println("Lambda 实现Runnable "));
  12. t2.start();
  13. //主线程干活
  14. for (int i = 0; i < 100; i++) {
  15. System.out.println("旺财:" + i);
  16. }
  17. }
  18. }
  19. //1 定义任务类,实现Runnable,并重写run方法
  20. class MyTask implements Runnable {
  21. @Override
  22. public void run() {
  23. //子线程干活
  24. for (int i = 0; i < 100; i++) {
  25. System.out.println("小强:" + i);
  26. }
  27. }
  28. }
优点 缺点
实现Runnable 扩展性强,实现该接口的同时还可以继承其他的类。 编程相对复杂,不能直接使用Thread类中的方法
继承Thread 编程比较简单,可以直接使用Thread类中的方法 可扩展性较差,
不能再继承其他的类

3 Thread常见方法

  1. public String getName():返回此线程的名称
  2. public void setName(String name):将此线程的名称更改为等于参数 name ,通过构造方法也可以设置线程名称
  3. public static Thread currentThread() :返回对当前正在执行的线程对象的引用
  4. public static void sleep(long time):让线程休眠指定的时间,单位为毫秒。 1s = 1000ms
  5. public void join() : 具备阻塞作用 , 等待这个线程死亡,才会执行其他线程
  6. public final void setPriority(int newPriority) 设置线程的优先级
  7. public final int getPriority() 获取线程的优先级

4 线程调度

线程有两种调度模型
分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

:Java使用的是抢占式调度模型

后续持续更新中。。。。

  • Lambda表达式

    Lambda表达式

    1 函数式编程思想

    在数学中,函数就是有输入量、输出量的一套计算方案。在编程技术中,函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。我们经常把支持函数式编程的编码风格称为Lambda表达式。

体验Lambda表达式

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.Comparator;
  4. import java.util.List;
  5. /*
  6. Lambda表达式体验 :
  7. */
  8. public class LambdaDemo {
  9. public static void main(String[] args) {
  10. //将集合要以降序排序
  11. List<Integer> list = new ArrayList<>();
  12. list.add(100);
  13. list.add(300);
  14. list.add(200);
  15. //集合以降序排序(以前的做法:匿名对象)
  16. Collections.sort(list, new Comparator<Integer>() {
  17. @Override
  18. public int compare(Integer o1, Integer o2) {
  19. return o2 - o1;
  20. }
  21. });
  22. //集合以降序排序(新做法:Lambda表达式)
  23. Collections.sort(list,
  24. (Integer o1, Integer o2) -> {
  25. return o2 - o1;
  26. }
  27. );
  28. }
  29. }

2 函数式接口

只有一个抽象方法需要重写的接口就称为函数式接口。函数式接口允许存在其他的静态方法,默认方法,私有方法等,只对抽象方法有限制。
为了标识接口是一个函数式接口,可以在接口之上加上一个注解: @FunctionalInterface以示区别。
在JDK中 java.util.function 包中的所有接口都是函数式接口。
我们之前学习的Comparator及后续学习线程时的Runnable也是函数式接口。

  1. @FunctionalInterface
  2. public interface Runnable {
  3. public abstract void run();
  4. }

我们也可以自定函数式接口

  1. @FunctionalInterface
  2. public interface Swim {
  3. public abstract void swimming();
  4. }

小结:

  1. 接口中只有一个抽象方法是需要被实现的就是函数式接口
  2. 函数式接口通常会有一个标志性注解:@FunctionalInterface

3 Lambda表达式格式

Lambda表达式其实就是对函数式接口匿名内部类的简写,简写到只有一个方法的重写。因此,Lambda表达式所表示的其实就是对函数式接口中抽象方法的重写。
Lambda表达式的标准语法:( 形参列表 ) -> { 方法体 }
就是将匿名内部类一直简化到只有一个方法的存在,而且方法可以进一步简化到只有参数列表,和方法体,中间加上语法箭头。

Lambda表达式的格式

  1. 格式:( 形式参数 ) -> { 代码块 }
  2. 形式参数: 和要写的函数式接口中抽象方法的参数列表一样
  3. ->: 由英文中画线和大于符号组成,固定写法。
  4. 方法体: 重写抽象方法的方法体

4 Lambda 省略规则

  1. 参数类型可以省略,但是有多个参数的情况下,不能只省略一个
  2. 如果参数有且仅有一个,那么小括号可以省略
  3. 如果代码块的语句只有一条,可以省略大括号和分号,甚至是return ```java

import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List;

/ Lambda表达式体验 : / public class LambdaDemo { public static void main(String[] args) { //将集合要以降序排序 List list = new ArrayList<>(); list.add(100); list.add(300); list.add(200);

  1. //省略前
  2. Collections.sort(list,
  3. (Integer o1, Integer o2) -> {
  4. return o2 - o1;
  5. }
  6. );
  7. //省略后
  8. Collections.sort(list, ( o1, o2) -> o2 - o1);
  9. }

}

```

5 Lambda表达式和匿名内部类区别

  1. 所需类型不同
    匿名内部类:可以是接口,也可以是抽象类,还可以是具体
    Lambda表达式:只能是函数式接口
  2. 使用限制不同
    如果接口中有且仅有一个抽象方法需要重写,可以使用Lambda表达式,也可以使用匿名内部类
    如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
  3. 实现原理不同
    匿名内部类:编译之后,产生一个单独的.class字节码文件
    Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成


Hi,这是一条已归档的小记

什么是归档?

当你不再需要一篇小记展示在信息列表,但又不想删掉它时,可以将它进行归档,归档后的小记将进入已归档列表。

如何归档小记?

点击小记右边的…按钮,点击「归档」,点击左侧多选按钮还可进行批量归档操作。
image.png