一、JDK1.8中的函数式接口和流

在jdk1.8中有一个包 java.util.function,存放的都是函数式接口
这么多函数式接口,大致分为这么几类,如下图:
image.png
代码演示一下用法,有个印象:

//R apply(T t);函数型接口,一个参数,一个返回值
Function function = t ->{return t.length();};
System.out.println(function.apply(“abcd”));

//boolean test(T t);断定型接口,一个参数,返回boolean
Predicate predicate = t->{return t.startsWith(“a”);};
System.out.println(predicate.test(“a”));

// void accept(T t);消费型接口,一个参数,没有返回值
Consumer consumer = t->{
System.out.println(t);
};
consumer.accept(“javaXXXX”);

//T get(); 供给型接口,无参数,有返回值
Supplier supplier =()->{return UUID.randomUUID().toString();};
System.out.println(supplier.get());
  1. package com.qfedu.day12;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import java.util.stream.Collectors;
  6. import java.util.stream.Stream;
  7. /**
  8. * @Author laoyan
  9. * @Description TODO
  10. * @Date 2022/3/16 9:55
  11. * @Version 1.0
  12. *
  13. * 请按照给出数据,找出
  14. * * 偶数ID
  15. * * 年龄大于24
  16. * * 用户名转为大写
  17. * * 用户名字母倒排序
  18. * * 只输出2个
  19. * //首先我们需要一个数据源 集合或者数组
  20. * List<User> list =new ArrayList<User>();
  21. * list.add(new User(11,"zhangsan",25));
  22. * list.add(new User(12,"lisi",25));
  23. * list.add(new User(13,"wangwu",35));
  24. * list.add(new User(14,"zhaoliu",15));
  25. * list.add(new User(15,"tanxin",38));
  26. * list.add(new User(16,"wang",45));
  27. */
  28. public class TestStream {
  29. public static void main(String[] args) {
  30. List<User> list =new ArrayList<User>();
  31. list.add(new User(11,"zhangsan",25));
  32. list.add(new User(12,"lisi",25));
  33. list.add(new User(13,"wangwu",35));
  34. list.add(new User(14,"zhaoliu",15));
  35. list.add(new User(15,"tanxin",38));
  36. list.add(new User(16,"wang",45));
  37. /**
  38. * 使用stream流
  39. */
  40. Stream<User> stream = list.stream();
  41. /*
  42. 可以通过工具类获取数组的Stream流
  43. String[] arr = {"张三"};
  44. Stream<String> stream1 = Arrays.stream(arr);
  45. */
  46. /**
  47. * 过滤 filter
  48. * 返回true 表示保留,返回false 代表过滤
  49. * map
  50. * 将集合中的元素一个个传递进来,然后经过处理再返回回去
  51. * limit
  52. * 限定输出的个数
  53. * foreach
  54. * 对集合中的元素循环遍历。
  55. * sorted
  56. * 排序,里面可以放入一个比较器
  57. * collect
  58. * 从一个集合中获取一个Stream流,通过流进行数据的处理,处理完之后,我们实际的集合里面的元素是不会发生改变的。
  59. * 我们如果想将处理完的流Stream对象变为list 集合,需要使用到 collect
  60. * 收集的意思: 可以收集成list , set
  61. * 该方法是流对象的方法,不能在forEach 方法后调用。
  62. */
  63. List<User> userList = stream.filter(user -> {
  64. if (user.getId() % 2 == 0) {
  65. return true;
  66. }
  67. return false;
  68. }).filter(user -> {
  69. if (user.getAge() > 24) {
  70. return true;
  71. }
  72. return false;
  73. }).map(user -> {
  74. String daXie = user.getName().toUpperCase();
  75. StringBuffer stringBuffer = new StringBuffer(daXie);
  76. System.out.println(stringBuffer.toString());
  77. user.setName(stringBuffer.reverse().toString());
  78. return user;
  79. }).sorted((u1, u2) ->
  80. //u1.getAge() - u2.getAge()
  81. u1.getName().compareTo(u2.getName())
  82. ).limit(2).collect(Collectors.toList());/*forEach(user->{
  83. System.out.println(user);
  84. })*/
  85. System.out.println(userList);
  86. }
  87. }

二、Stream(流)

流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”
Stream 自己不会存储元素,Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
一般开发的套路是:创建一个Stream:一个数据源(数组、集合)—>中间操作:一个中间操作,处理数据源数据—>终止操作:一个终止操作,执行中间操作链,产生结果.
案例:

/*
请按照给出数据,找出
偶数ID
年龄大于24
用户名转为大写
用户名字母倒排序
只输出一个
用户名字
*/

举例:

请按照给出数据,找出
 * 偶数ID
 * 年龄大于24
 * 用户名转为大写
 * 用户名字母倒排序
 * 只输出2个
 //首先我们需要一个数据源  集合或者数组
        List<User> list =new ArrayList<User>();
        list.add(new User(11,"zhangsan",25));
        list.add(new User(12,"lisi",25));
        list.add(new User(13,"wangwu",35));
        list.add(new User(14,"zhaoliu",15));
        list.add(new User(15,"tanxin",38));
        list.add(new User(16,"wang",45));
package com.qfedu.stream;

public class User {

    private int id;
    private String name;
    private int age;

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package com.qfedu.stream;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;

/*
 * 请按照给出数据,找出
 * 偶数ID
 * 年龄大于24
 * 用户名转为大写
 * 用户名字母倒排序
 * 只输出2个
 *
 *   Stream   流  很重要,因为现在很多地方都这么写了,一定要重视一下
 *     常用的方法:
 *        filter  接收的是断定型接口
 *        map     接收的是函数型接口
 *        sorted   接收一个比较器
 *        forEach  接收一个消费性接口
 *        limit  表示显示多少行
 *
 */
public class TestStreamMain {

    public static void main(String[] args) {
        //首先我们需要一个数据源  集合或者数组
        List<User> list =new ArrayList<User>();
        list.add(new User(11,"zhangsan",25));
        list.add(new User(12,"lisi",25));
        list.add(new User(13,"wangwu",35));
        list.add(new User(14,"zhaoliu",15));
        list.add(new User(15,"tanxin",38));
        list.add(new User(16,"wang",45));

        Stream<User> stream = list.stream();
        // filter 过滤   里面需要接收一个断定型接口
        // 因为是对list集合进行遍历,所以p指的就是一个个的user
        stream.filter( p-> {
            if(p.getId()%2==0){
                return true;
            }
            return false;
            // filter 可以用来过滤数据
        }).filter( p->{
            if(p.getAge()>24){
                return true;
            }
            return false;
            // map 可以用来修改数据
        }).map( p->{
            p.setName(p.getName().toUpperCase());
            return p;
            // sorted 对数据按照一定规则排序,里面使用到了比较器这样一个接口
        }).sorted((user1, user2) -> {
            return user2.getName().compareTo(user1.getName());
        }).limit(1)
        .forEach(System.out::println); //等同于如下操作
        /*forEach((t)->{
            System.out.println(t);
        })*/;

    }
}

三、集合流加强版

4.1.2.1. 集合流的介绍

集合流,使用类Stream来表示,是Java8的新特性之一。使用集合流可以对集合的操作进行功能增强,流并不是集合的元素,也不是一种数据结构,不负责数据的存储操作。更像是迭代器,可以实现对集合中的元素进行单向不循环的遍历。

在我们对集合中的元素进行操作的时候,有时候需要使用到其他操作的结果。

例如: 在一个存储了所有的考试成绩的集合中,我需要计算及格部分的最低成绩是多少。

对于这样的需求,我需要先提取出来集合中满足条件的元素(及格的成绩),然后再计算最低成绩。

在这个过程中,集合的流式编程可以大幅度的简化代码的数量。将数据源中的数据,读取到一个流中,可以对这个流中的数据进行操作(删除、过滤、映射…)。每次的操作结果也是一个流对象,可以对这个流再进行其他的操作。

4.1.2.2. 使用流式编程的步骤

使用流式编程来操作集合中的元素,可以分为三步:

  • 读取数据源的数据,构建流对象
  • 对流中的数据进行各种处理
  • 对处理之后的流中的数据,进行聚合的操作

其中,第二部分的操作,我们可以对流中的数据进行各种各样的处理。每一次的处理结果得到的也是一个流对象本身。可以使用这个返回值(流对象)继续进行其他的处理操作。因此这样的称为 — 中间操作。

第三部分的操作,我们对流中的数据进行聚合,得到的不再是一个流对象,而是具体的数据了。这样的操作称为 — 最终操作。

中间操作和最终操作的方法,基本都是以一个或多个函数式接口为参数的,因此需要熟练掌握Lambda表达式,才能体会到流式编程的强大之处。

4.1.2.3. 读取数据源

顾名思义,这一步的操作就是将一个集合作为数据源,读取其中的元素,构建一个Stream对象。此时Stream对象中将包含集合中的所有元素,我们可以使用流式的操作,对元素进行各种处理。

通常情况下,我们会使用Collection集合,或者数组作为数据源。

注意: Map是不能作为数据源的!

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

// 1. 通过Collection接口中的stream()方法获取数据源为Collection的流
Stream<Integer> stream = list.stream();

// 2. 通过Collection接口的parallelStream()方法获取数据源为Collection的流
Stream<Integer> stream = list.parallelStream();

// 3. 通过Arrays工具类中的stream()方法获取数据源为数组的流
IntStream stream = Arrays.stream(array);

关于 stream() 和 parallelStream()

他们都是Collection集合获取数据源的方法,不同点在于stream()方法获取的数据源是串行的,parallelStream()获取的数据源是并行的。parallelStream()内部集成了多个线程对流中的数据进行操作,效率更高。

4.1.2.4. 最终操作

我们先来学习最终操作,是因为即便我们知道了如何对数据源中的元素进行一些中间操作,例如去重、排序、截取、过滤等操作,我们最终得到的还是一个Stream对象,无法直观的看到这个Stream种包含什么元素,也就不知道我们的操作是否是成功的。所以我们先学习最终操作。知道了如何对流中的数据进行提取和统计的计算之后,再来学习中间操作。

注意事项说在前:

最终操作,之所以叫最终操作。除了这个操作得到的不再是流对象Stream,我们没有办法继续流式的操作下去之外。还有一个很重要的因素: 最终操作会关闭这个流对象本身,我们对一个已经关闭的流进行元素的处理,会有异常!

因此,我们接下来的最终操作的学习过程中切记,千万不要对一个已经关闭的流进行元素的处理。

collect
// 将流中的元素聚合到一起,最常见的做法就是将元素都搜集起来,存入一个集合里面。

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

// 1.1. 转成 List
List<Integer> result1 = list.stream().collect(Collectors.toList());
System.out.println(result1);

// 1.2. 转成 Set
Set<Integer> result2 = list.stream().collect(Collectors.toSet());
System.out.println(result2);

// 1.3. 转成 Map,提供两个函数式接口的实现,分别实现键的生成规则和值的生成规则
Map<Integer, Integer> result3 = list.stream().collect(Collectors.toMap(ele -> ele / 10, ele -> ele));
System.out.println(result3);

reduce —合并操作

// 实例化一个集合对象,并进行元素的添加
ArrayList list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);  
// 将流的元素,逐一带入到这个方法中,进行运算
// 最终的运算结果,得到的其实是一个 Optional 类型,需要使用 get() 获取到里面的数据
int result4 = list.stream().reduce((e1, e2) -> e1 + e2).get();
System.out.println(result4);

count

// 实例化一个集合对象,并进行元素的添加
ArrayList list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);  
// 统计流中的元素的数量
long result5 = list.stream().count();
System.out.println(result5);

forEach

// 实例化一个集合对象,并进行元素的添加
ArrayList list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20); 
// 迭代、遍历流中的元素
list.stream().forEach(System.out::println);

max&min
// 获取流中的元素的最大值、最小值

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

// 获取最大值
Integer result6 = list.stream().max(Integer::compareTo).get();
System.out.println("max is : " + result6);
// 获取最小值
Integer result7 = list.stream().min(Integer::compareTo).get();
System.out.println("min is : " + result7);

anyMatch&allMatch&noneMatch
// allMatch: 只有当流中所有的元素,都匹配指定的规则,才会返回 true
// anyMatch: 只要流中有任意的数据,满足指定的规则,都会返回 true
// noneMatch: 只有当流中的所有的元素,都不满足指定的规则,才会返回true

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

// 判断流中是否所有的元素都大于 50
boolean result8 = list.stream().allMatch(ele -> ele > 50);
System.out.println(result8);

// 判断流中是否有大于 50 的数据
boolean result9 = list.stream().anyMatch(ele -> ele > 50);
System.out.println(result9);

// 判断流中是否没有奇数
boolean result10 = list.stream().noneMatch(ele -> ele % 2 != 0);
System.out.println(result10);

find
// findFirst: 从流中获取一个元素(一般情况下,是获取的开头的元素)
// findAny: 从流中获取一个元素(一般情况下,是获取的开头的元素)
// 这两个方法,绝大部分情况下,是完全相同的,但是在多线程的环境下,findAny和find返回的结果可能不一样。

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

Integer result11 = list.parallelStream().findFirst().get();
System.out.println(result11);

Integer result12 = list.parallelStream().findAny().get();
System.out.println(result12);

List<String> lst1 = Arrays.asList("Jhonny", "David", "Jack", "Duke", "Jill","Dany","Julia","Jenish","Divya");
        List<String> lst2 = Arrays.asList("Jhonny", "David", "Jack", "Duke", "Jill","Dany","Julia","Jenish","Divya");

        Optional<String> findFirst = lst1.parallelStream().filter(s -> s.startsWith("D")).findFirst();
        Optional<String> fidnAny = lst2.parallelStream().filter(s -> s.startsWith("J")).findAny();

        System.out.println(findFirst.get()); //总是打印出David
        System.out.println(fidnAny.get()); //会随机地打印出Jack/Jill/Julia

异常演示
// 最终操作会关闭流,此时再使用这个流进行其他的中间或最终操作都会有问题

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

// 获取流对象
Stream<Integer> stream = list.stream();
// 已经进行了最终操作,流对象已经关闭
long count = stream.count();
// 使用已经关闭的流,继续进行其他的操作,异常
stream.forEach(System.out::println);
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at com.qf.cstream.FinalOperationDemo.main(FinalOperationDemo.java:78)

4.1.2.5. 中间操作

将数据从数据源中读取到流中,中间操作,就是对流中的数据进行各种各样的操作、处理。中间操作可以连续操作,每一个操作的返回值都是一个Stream对象,可以继续进行其他的操作。直到最终操作。

filter
// 条件过滤,仅保留集合中满足条件的数据

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

list.stream().filter(ele -> ele.length() > 5).forEach(System.out::println);

distinct
// 去除集合中重复的元素,无参的方法,去重的规则与HashSet相同:
// 1. 先比较hashCode,如果不相同,视为不重复的元素
// 2. 如果hashCode相同,再比较equals,如果不相同,视为不重复的元素
// 3. 如果hashCode相同,equals比较也相同,视为重复的元素

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

list.stream().distinct().forEach(System.out::println);

sorted
// 对流中的元素进行排序
// 无参: 使用元素对应的类实现的Comparable接口的实现,进行元素的大小比较
// 有参: 使用参数Comparator接口的实现,按照指定的规则进行元素的大小比较

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

// 按照流中的元素对应的类,实现的 Comparable 接口中的方法实现排序
list.stream().sorted().forEach(System.out::println);
// 将流中的数据,按照指定的规则进行排序
list.stream().sorted((e1, e2) -> e1.length() - e2.length()).forEach(System.out::println);

limit&skip
// limit: 限制,截取流中指定数量的元素
// skip: 跳过,跳过流中的指定数量的元素

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

// 跳过开头的指定的元素
list.stream().skip(3).forEach(System.out::println);
// 截取指定数量的元素
list.stream().limit(3).forEach(System.out::println);
// 配合使用,截取部分
list.stream().skip(5).limit(2).forEach(System.out::println);

map
// 元素映射,将流中的元素按照指定的规则进行一对一的替换

// 实例化一个集合对象,并进行元素的添加
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 20, 5, 18, 9, 21, 22, 21, 20, 92, 9, 20);

list.stream().map(ele -> ele + ".txt").forEach(System.out::println);

mapToInt
// 将流中的元素映射成int类型的数据,得到的结果是IntStream对象,方便进行数据的统计

double avg = list.stream().mapToInt(Person::getAge).average().getAsDouble();
System.out.println(avg);

flatMap
String[] strs = { “hello”, “world” };

// 这里是一个直接映射,将流中的元素(字符串),替换成了由这个字符串分解出来的字符数组。
// 在映射结束后,流中的数据依然是两个,分别是两个字符数组
Arrays.stream(strs).map(String::toCharArray).forEach(ele -> System.out.println(Arrays.toString(ele)));

// flatMap: 扁平化映射
//          常用于map直接映射完成后,流中的数据是一个个的容器,而我们需要对容器中的元素进行处理
//          此时,可以使用扁平化映射,将容器中的元素直接存入流中
Arrays.stream(strs).map(str -> str.split(""))
    .flatMap(Arrays::stream)
    .distinct()
    .forEach(System.out::println);

4.1.2.6. 实战案例

需求: 一个集合中存储了了若干个Student对象, 要求查询出以下结果:
1. 所有及格的学生信息
2. 所有及格的学生姓名
3. 所有学生的平均成绩
4. 班级的前3名(按照成绩)
5. 班级的3-10名(按照成绩)
6. 所有不不及格的学生平均成绩
7. 将及格的学生, 按照成绩降序输出所有信息 
8. 班级学生的总分


public class Student {
    private String name;
    private int score;

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return String.format("姓名: %s, 成绩: %d", name, score);
    }
}

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.stream.Collectors;

public class Program {
    public static void main(String[] args) {
        // 0. 实例化集合,存储学生对象
        List<Student> list = new ArrayList<>();
        Collections.addAll(list,
                           new Student("xiaoming", 89),
                           new Student("xiaobai", 98),
                           new Student("xiaohei", 78),
                           new Student("xiaolv", 86),
                           new Student("xiaowang", 59),
                           new Student("xiaoxiao", 100)
                          );

        // 1. 所有及格的学生信息
        System.out.println("-----------1. 所有及格的学生信息-----------");
        list.stream()
            .filter(s -> s.getScore() >=60)
            .forEach(System.out::println);

        // 2. 所有及格的学生姓名
        System.out.println("-----------2. 所有及格的学生姓名-----------");
        list.stream()
            .filter(s -> s.getScore() >= 60)
            .map(Student::getName)
            .forEach(System.out::println);

        // 3. 所有学生的平均成绩
        System.out.println("-----------3. 所有学生的平均成绩-----------");
        double average = list.stream()
            .mapToInt(Student::getScore)
            .average()
            .getAsDouble();
        System.out.println(average);


        // 4. 班级的前3名(按照成绩)
        System.out.println("-----------4. 班级的前3名(按照成绩)-----------");
        List<Student> result1 = list.stream()
            .sorted((s1, s2) -> s2.getScore() - s1.getScore())
            .limit(3)
            .collect(Collectors.toList());
        result1.forEach(System.out::println);



        // 5. 班级的3-10名(按照成绩)
        System.out.println("-----------5. 班级的3-10名(按照成绩)-----------");
        List<Student> result2 = list.stream()
            .sorted((s1, s2) -> s2.getScore() - s1.getScore())
            .limit(10)
            .skip(2)
            .collect(Collectors.toList());
        result2.forEach(System.out::println);


        // 6. 所有不不及格的学生平均成绩
        System.out.println("-----------6. 所有不不及格的学生平均成绩-----------");
        double average1 = list.stream()
            .filter(s -> s.getScore() < 60)
            .mapToInt(Student::getScore)
            .average()
            .getAsDouble();
        System.out.println(average1);


        // 7. 将及格的学生, 按照成绩降序输出所有信息
        System.out.println("-----------7. 将及格的学生, 按照成绩降序输出所有信息-----------");
        list.stream()
            .filter(s -> s.getScore() >= 60)
            .sorted((s1, s2) -> s2.getScore() - s1.getScore())
            .forEach(System.out::println);


        // 8. 班级学生的总分
        System.out.println("-----------8. 班级学生的总分-----------");
        long sum = list.stream()
            .mapToInt(Student::getScore)
            .sum();
        System.out.println(sum);
    }
}