1. 概述

​ Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

特点:

  • 不是数据结构,不会保存数据。

  • 不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中。(保留意见:毕竟peek方法可以修改流中元素)

  • 惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。

2. 分类

类型 状态 API
中间操作 有状态 unordered() filter() map() mapToInt() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek()
中间操作 无状态 distinct() sorted() limit() skip()
结束操作 非短路操作 foreach() forEachOrdered() toArray() reduce() collect() max() min() count()
结束操作 短路操作 anyMatch() allMatch() noneMatch() findFirst() findAny()

注释:

  • 无状态:指元素的处理不受之前元素的影响;

  • 有状态:指该操作只有拿到所有元素之后才能继续下去。

  • 非短路操作:指必须处理所有元素才能得到最终结果;

  • 短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。

3. 使用

3.1 流的常用创建方法

3.1.1使用Collection下的 stream()parallelStream() 方法

1
2
3
4
5
6
7
8
9
10
public static void method1() {
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(3);
list.add(5);
list.add(7);
list.add(9);
Stream<Integer> stream = list.stream();
Stream<Integer> parallelStream = list.parallelStream();
}

3.1.2 使用Arrays中的 stream() 方法,将数组转成流

1
2
3
4
5
6
7
8
public static void method2() {
Double[] nums = new Double[10];
for (int i = 0; i < 10; i++) {
nums[i] = random();
}
Stream<Double> stream = Arrays.stream(nums);
stream.forEach(System.out::println);
}

3.1.3 使用Stream中的静态方法: of()iterate()generate()

1
2
3
4
5
6
7
8
9
10
public static void method3() {
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6);
stream1.forEach(System.out::println);

Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 1).limit(6);
stream2.forEach(System.out::println);

Stream<Double> stream3 = Stream.generate(Math::random).limit(6);
stream3.forEach(System.out::println);
}

3.1.4 使用 BufferedReader.lines() 方法,将每行内容转成流

1
2
3
4
5
public static void method4() throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("D:/Java/Test/K1.txt"));
Stream<String> lines = reader.lines();
lines.forEach(System.out::println);
}

3.1.5 使用 Pattern.splitAsStream() 方法,将字符串分隔成流

1
2
3
4
5
public static void method5() {
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("K,H,I,G,H,N,E,S,S");
stringStream.forEach(System.out::println);
}

3.2 流的中间操作

3.2.1 筛选与切片

  • filter():过滤流中的某些元素
  • limit():获取前n个元素
  • skip():跳过前n个元素
  • limit + skip:可以实现分页skip(PageNumber * PageSize).limit(PageSize)
  • distinct():通过流中元素的hashCode()equals()去除重复元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void method1() {
List<Integer> list = Arrays.asList( 1, 1, 1,
2, 2, 2,
3, 3, 3,
4, 4, 4,
5, 5, 5,
6, 6, 6,
7, 7, 7,
8, 8, 8,
9, 9, 9);

System.out.println("----------过滤大于5的元素----------");
Stream<Integer> stream1 = list.stream().filter(x -> x <= 5);
stream1.forEach(System.out::println);

System.out.println("----------分页查询/5/3----------");
Stream<Integer> stream2 = list.stream().skip(5 * 3).limit(3);
stream2.forEach(System.out::println);

System.out.println("----------去掉重复元素----------");
Stream stream3 = list.stream().distinct();
stream3.forEach(System.out::println);
}

3.2.2 映射

  • map():接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
  • flatMap():接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static void method2() {
    List<String> list = Arrays.asList("A,B,C", "1,2,3");

    Stream<String> stringStream1 = list.stream().map( x -> x.replaceAll(",", "|"));
    stringStream1.forEach(System.out::println);

    Stream<String> stringStream2 = list.stream().flatMap( x ->{
    String[] split = x.split(",");
    Stream<String> stringStream = Arrays.stream(split);
    return stringStream;
    });
    stringStream2.forEach(System.out::println);
    }

3.2.3 排序

  • sorted(): 自然排序,流中元素需要实现Comparable接口
  • sorted(Comparator c): 自定义排序,自定义Comparator排序器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public static void method3() {
    List<Integer> integers = Arrays.asList(1, 8, 2, 3, 6, 7, 6, 3, 7, 2);
    integers.stream().sorted().forEach(System.out::println);

    List<String> strings = Arrays.asList("K", "H", "I", "G", "H", "N", "E", "S", "S");
    strings.stream().sorted(
    (s1, s2) -> {
    return s1.compareTo(s2);
    }
    ).forEach(System.out::println);

    @Data
    @AllArgsConstructor
    class Student {
    String name;
    int score;
    }
    List<Student> students = Arrays.asList( new Student("K", 100),
    new Student("H", 91),
    new Student("I", 95),
    new Student("G", 98),
    new Student("N", 88));
    students.stream().sorted(
    (s1, s2) -> {
    return s1.score - s2.score;
    }
    ).forEach(System.out::println);
    }

3.2.4 消费

  • peek():接收Consumer表达式,无返回值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public static void method4() {
    @Data
    @AllArgsConstructor
    class Student {
    String name;
    int score;
    }
    List<Student> students = Arrays.asList( new Student("K", 100),
    new Student("H", 91),
    new Student("I", 95),
    new Student("G", 98),
    new Student("N", 88));
    students.stream().peek(
    s -> {
    s.setScore(99);
    }
    ).forEach(System.out::println);
    }

4. 流的终止操作

4.1 匹配、聚合操作

  • allMatch():接收一个Predicate函数,当流中每个元素都符合该断言时才返回true,否则返回false
  • noneMatch():接收一个Predicate函数,当流中每个元素都不符合该断言时才返回true,否则返回false
  • anyMatch():接收一个Predicate函数,只要流中有一个元素满足该断言则返回true,否则返回false
  • findFIrst():返回流中第一个元素
  • findAny():返回流中的任意元素
  • count():返回流中元素总个数
  • max():返回流中元素的最大值
  • min():返回流中元素的最小值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void method1() {
System.out.println("----------method1----------");
List<Integer> list = Arrays.asList(1, 8, 2, 3, 6, 7, 6, 3, 7, 2);

boolean allMatch = list.stream().anyMatch(e -> e > 10); // false
boolean noneMatch = list.stream().noneMatch(e -> e > 10); // true
boolean anyMatch = list.stream().anyMatch(e -> e > 4); // true
System.out.println(allMatch + " " + noneMatch + " " + anyMatch);

Integer findFirst = list.stream().findFirst().get();
Integer findAny = list.stream().findAny().get();
System.out.println("first = " + findFirst + ", any = " + findAny);

long count = list.stream().count();
Integer max = list.stream().max(Integer::compareTo).get();
Integer min = list.stream().min(Integer::compareTo).get();
System.out.println("count = " + count + ", max = " + max + ", min = " + min);
}

4.2 规约操作

  • Optional<T> reduce(BinaryOperator<T> accumulator):第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。
  • T reduce(T identity, BinaryOperator<T> accumulator):流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
  • <U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner):在串行流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。在并行流(parallelStream)中,我们知道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity, accumulator)一样,而第三个参数combiner函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行规约。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public static void method2() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 求和
Integer v = list.stream().reduce(0, (x1, x2) -> x1 + x2);
System.out.println("sum = " + v);

// 10 + 和
Integer v1 = list.stream().reduce(10, (x1, x2) -> x1 + x2);
System.out.println("10 + sum = " + v1);

// 求-和
Integer v2 = list.stream().reduce(0,
(x1, x2) -> {
System.out.println("stream accumulator: x1: " + x1 + " x2: " + x2);
return x1 - x2;
}
);
System.out.println(v2);

// 求累积
Integer v3 = list.stream().reduce(1,
(x1, x2) -> {
System.out.println("stream combiner: x1:" + x1 + " x2:" + x2);
return x1 * x2;
}
);
System.out.println(v3);

// 字符串拼接
List<String> list1 = Arrays.asList("K", "Highness", ". Nice ", "to ", "meet ", "you");
String res = list1.stream().reduce("Hello ", (c1, c2) -> {
return c1 + c2;
});
System.out.println(res);
}

4.3 收集操作

collect():接收一个Collector实例,将流中元素收集成另外一个数据结构。

Collector<T, A, R> 是一个接口,有以下5个抽象方法:

  • Supplier<A> supplier():创建一个结果容器A。
  • BiConsumer<A, T> accumulator():消费型接口,第一个参数为容器A,第二个参数为流中元素T。
  • BinaryOperator<A> combiner():函数接口,该参数的作用跟上一个方法(reduce)中的combiner参数一样,将并行流中各个子进程的运行结果(accumulator函数操作后的容器A)进行合并。
  • Function<A, R> finisher():函数式接口,参数为容器A,返回类型为collect方法最终想要的结果R。
  • Set<Characteristics> characteristics():返回一个不可变的Set集合,用来表明该Collector的特征。

有以下三个特征:

  • CONCURRENT:表示此收集器支持并发。
  • UNORDERED:表示该收集操作不会保留流中元素原有的顺序。
  • IDENTITY_FINISH:表示finisher参数只是标识而已,可忽略。