文章目录

  • 一、Stream简介
  • 二、Stream的构成及操作类型
  • 1、Stream构成
  • 2、操作类型
  • 三、Stream常用属性
  • 1、collect 收集属性
  • 2、map / flatMap 映射转换
  • 3、filter 过滤属性
  • 4、forEach / peek 遍历属性
  • 5、reduce 聚合属性
  • 6、sorted 排序属性
  • 7、match 匹配属性


一、Stream简介

Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作(bulk data operation)。
Stream API 借助于同样新出现的 Lambda 表达式,而且使用并发模式,极大的提高编程效率和程序可读性。

  • 举个栗子
public static void main(String[] args) {
    List<User> userList = new ArrayList<>();
    userList.add(new User(3, "Jack"));
    userList.add(new User(1, "Many"));
    userList.add(new User(2, "Amy"));

    // java8之前的排序方式
    Collections.sort(userList, new Comparator<User>() {
        @Override
        public int compare(User o1, User o2) {
            return o1.id.compareTo(o2.id);
        }
    });

    // 使用java8 Stream排序
    userList = userList.stream()
            .sorted(Comparator.comparing(User::getId)).collect(Collectors.toList());
}

Java 8 使用 Stream,代码更加简洁易读;程序执行速度更快。

二、Stream的构成及操作类型

1、Stream构成

  • 当我们使用一个流(也就是Stream)的时候,通常包括三个基本步骤:
    获取一个数据源(source)→ 数据转换→执行操作获取想要的结果。

2、操作类型

(1)Intermediate(中间的)
一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
(2)Terminal(末端的)
一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果。

  • 举个栗子
public static void main(String[] args) {
    List<Integer> numList = Arrays.asList(new Integer[]{5, 2, 9, 1, 5, 7, 3, 8, 6});
    numList = numList.stream().sorted().filter(num -> num % 2 == 1)
            .collect(Collectors.toList());
    System.out.println(numList);             // 输出:[1, 3, 5, 5, 7, 9]
}

其中,sorted和filter操作作为中间层进行排序和筛选,collect操作作为结束层重新放到List结果中

  • 常见的操作可以归类如下
  • Intermediate:
    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
  • Terminal:
    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
三、Stream常用属性

1、collect 收集属性

collect:将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法

public static void main(String[] args) {
        Integer[] numArr = {1, 2, 3, 4, 5};

        List<Integer> numList = Stream.of(numArr).collect(Collectors.toList());
    }

2、map / flatMap 映射转换

public static void main(String[] args) {
    List<User> userList = new ArrayList<>();
    userList.add(new User(3, "Jack"));
    userList.add(new User(1, "Many"));
    userList.add(new User(2, "Amy"));

    // 提取User的Name转成List集合
    List<String> nameList = userList.stream()
            .map(User::getName).collect(Collectors.toList());
    System.out.println(nameList);            // 输出[Jack, Many, Amy]
}

从上面例子可以看出,map 生成的是个 1:1 映射,每个输入元素,都按照规则(例子中的User::getName)转换成为另外一个元素。
还有一些场景,是一对多映射关系的,这时需要 flatMap。

public static void main(String[] args) {
    List<User> userList1 = new ArrayList<>();
    userList1.add(new User(3, "Jack"));
    userList1.add(new User(1, "Many"));
    userList1.add(new User(2, "Amy"));

    List<User> userList2 = new ArrayList<>();
    userList2.add(new User(11, "张三"));
    userList2.add(new User(12, "李四"));

    List<String> nameList = Stream.of(userList1, userList2)
            .flatMap(list -> list.stream()).map(User::getName)
            .collect(Collectors.toList());

    System.out.println(nameList);            // 输出[Jack, Many, Amy, 张三, 李四]
}

3、filter 过滤属性

filter 对原始 Stream 进行某项筛选,通过筛选的元素被留下来生成一个新 Stream。

public static void main(String[] args) {
    List<Integer> numList = Arrays.asList(new Integer[] {1, 2, 3, 4, 5, 6});
    // 取出集合中的所有偶数
    numList = numList.stream()
            .filter(num -> num % 2 == 0).collect(Collectors.toList());
    System.out.println(numList );            // 输出[2, 4, 6]
}

4、forEach / peek 遍历属性

forEach 是 terminal 操作,方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。

public static void main(String[] args) {
    List<User> userList = new ArrayList<>();
    userList.add(new User(3, "Jack"));
    userList.add(new User(1, "Many"));
    userList.add(new User(2, "Amy"));

    // 使用forEach属性遍历集合
    userList.stream().forEach(user -> {
        System.out.println(user.getName());
    });
}

注意:forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,无法对一个 Stream 进行两次 terminal 运算。
相反,具有相似功能的 intermediate 操作 peek属性可以达到上述目的,
仍可对stream做再次操作。

public static void main(String[] args) {
    List<User> userList = new ArrayList<>();
    userList.add(new User(3, "Jack"));
    userList.add(new User(1, "Many"));
    userList.add(new User(2, "Amy"));

    // 使用peek属性遍历集合操作
    userList = userList.stream().peek(user -> {
        user.setName(user.getName() + "1");
    }).collect(Collectors.toList());

    System.out.println(userList);        // 输出[User{id=3, name='Jack1'}, User{id=1, name='Many1'}, User{id=2, name='Amy1'}]
}

5、reduce 聚合属性

主要作用是把 Stream 元素组合起来。
它提供一个起始值(种子),然后依照运算规则,和前面 Stream 的第一个、第二个、第 n 个元素组合。
从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。

public static void main(String[] args) {
    // 字符串拼接
    String[] arr = {"a", "b", "c", "d"};
    String content = Stream.of(arr).reduce("", String::concat);
    System.out.println(content);        //  输出 abcd

    // 求和, 需要一个起始参数值
    int sum = Stream.of(1, 2, 3, 4, 5, -2).reduce(0, Integer::sum);
    System.out.println(sum);            // 输出 13

    // 找最大值
    int maxNum = Stream.of(1, 3, 5, -2, 0).reduce(Integer::max).get();
    System.out.println(maxNum);        // 输出 5
}

6、sorted 排序属性

对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。

public static void main(String[] args) {
    Integer[] numArr = {2, 3, 1, 5, 0 ,8};
    List<Integer> numList = Stream.of(numArr)
            .sorted().collect(Collectors.toList());
    System.out.println(numList);        // 输出[0, 1, 2, 3, 5, 8]

    // 反向排序
    numList = Stream.of(numArr)
            .sorted(Comparator.reverseOrder()).collect(Collectors.toList());
    System.out.println(numList);        // 输出[8, 5, 3, 2, 1, 0]
}

7、match 匹配属性

Stream 有三个 match 方法

属性

描述

结果

allMatch

Stream 中全部元素符合传入的 predicate

true

anyMatch

Stream 中只要有一个元素符合传入的 predicate

true

noneMatch

Stream 中没有一个元素符合传入的 predicate

true

public static void main(String[] args) {
    Integer[] numArr = {1, 2, 3, 4, 5};

    boolean flag1 = Stream.of(numArr).allMatch(num -> num > 1);
    System.out.println(flag1);            // 全部属性都大于1    输出false

    boolean flag2 = Stream.of(numArr).anyMatch(num -> num > 1);
    System.out.println(flag2);            // 部分属性大于1    输出true

    boolean flag3 = Stream.of(numArr).noneMatch(num -> num > 1);
    System.out.println(flag3);            // 没有大于1的属性    输出false
}