Java---Stream入门

由于本文需要有一定的Lambda基础,所以如果不懂什么是Lambda的同学请移步:Java---Lambda

学习Stream的目的

  • 函数式编程渐渐变成主流,而Stream是函数式编程的重点。

  • 相对于传统的编程方式,代码更为简洁清晰易懂。

  • 使得并发编程变得如此简单。

  • 有效的避免了代码嵌套地狱。(见样例)

    if (条件1) {
        if (条件2) {
            if (条件3) {
                // 再嵌套下去都快见到Diablo了。
            }
        }
    }
    

Stream的特点

  • 不修改数据源:任何对于Stream对象的操作都不会修改数据源的数据。
  • 延迟执行:中间操作只是对于一系列操作细节的定义,而非执行。只有在终端操作被调用的同时执行中间操作。
  • 可消费:任何一个Stream对象执行了终端操作后都将不可再利用。只能操作由数据源生成的新的Stream。

Stream的分类

  • 串行流:单项环境下的Stream,基础。

    List.of().stream(); // 获取串行流
    
  • 并行流:多线程环境下的Stream,重点难点。

    List.of().parallelStream(); // 获取并行流
    

Stream对象的创建

总共有三种方式:

  • 经由集合对象创建

    List<String> list = new ArrayList<>();
    Stream<String> stream = list.stream(); // 创建串行流
    Stream<String> stream = list.parallelStream(); // 创建并行流
    
  • 经由数组对象创建(两种)

    String[] strs = new String[10];
    // 将数组所有元素装入stream
    Stream<String> stream1 = Arrays.stream(strs);
    // 将数组指定区间的元素装入stream
    Stream<String> stream2 = Arrays.stream(strs, 1, 7);
    
  • 使用Stream的静态方法创建(三种)

    // 由单个元素创建Stream,元素不允许为null
    Stream<String> stream1 = Stream.of("Test");
    // 由单个元素创建stream,元素允许为null
    Stream<String> stream2 = Stream.ofNullable(null);
    // 由多个元素创建stream,内部其实调用的是Arrays.stream(T[] array)
    Stream<String> stream3 = Stream.of("Test1", "Test2", "Test3");
    

方法的分类

  • 中间操作:根据调用的方法,返回各种各样的stream对象。传入的各种Lambda只是修改了该对象中对应方法的定义,而非执行。无并行流专用的方法。
  • 终端操作:执行终端操作的方法,并且其间也执行中间操作对应的方法。有并行流专用的方法。

中间操作

distinct

方法签名:Stream<T> distinct()
作用:返回一个去重后的Stream。

List<String> list = List.of("1", "1");
list.stream().distinct()
    .forEach(t -> System.out.println(t)); // 输出:1

filter

方法签名:Stream<T> filter(Predicate<? super T> predicate)
作用:返回一个由满足predicate条件的元素构成的Stream。

List<Integer> list = List.of(1, 3, 5);
list.stream().filter(t -> t >= 3)
    .forEach(t -> System.out.println(t)); // 输出:3, 5

sorted

因为sorted存在两种重载,并且在jdk源码的实现并不相同,所以我们分开讨论。

方法签名:Stream<T> sorted()
作用:通过调用T类型重写Comparable接口的compareTo方法排序,返回排序后的Stream。

// 此处省略了一些java文件定义的结构,请着眼于一下核心代码
// NG例, 不实现Comparable接口
class MyInteger {
    int value;
    MyInteger(int value) {
    	this.value = value;
    }
}
List<MyInteger> list = new ArrayList<>();
list.add(new MyInteger(4));
list.add(new MyInteger(1));
list.add(new MyInteger(3));
list.stream().sorted() // 不报错
    .forEach(t -> System.out.println(t.value)); // ClassCastException:不能被强转成Comparable类型

// OK例, 实现Comparable接口(Integer实现了Comparable接口)
List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().sorted()
    .forEach(t -> System.out.println(t)); // 输出:1 3 4

方法签名:Stream<T> sorted(Comparator<? super T> comparator)
作用:通过调用传入的comparator的compare方法排序,返回排序后的Stream。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().sorted((o1, o2) -> o2 - o1) // 传入一个比较器的实现
    .forEach(t -> System.out.println(t.value)); // 输出:4 3 1

skip

方法签名:Stream<T> skip(long n)
作用:返回一个不包含前n项的Stream。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().skip(2) // 跳过前两个元素
    .forEach(t -> System.out.println(t.value)); // 输出:3

limit

方法签名:Stream<T> limit(long n)
作用:返回一个包含前n项的Stream。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().limit(2) // 跳过前两个元素
    .forEach(t -> System.out.println(t.value)); // 输出:4 1

peek

方法签名:Stream<T> peek(Consumer<? super T> action)
作用:执行消费操作,返回原Stream。虽然有消费操作,但是Stream的状态并不会改变。并不会真正消费Stream这一特点是的peek方法常用于调试。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Stream<Integer> stream = list.stream();
stream.peek(t -> System.out.println(t)); // 输出:4 1 3, 并且不消费stream
stream.forEach(t -> System.out.println(t)); // 输出:4 1 3, 并且消费stream(消费后stream不可再次使用)
stream.forEach(t -> System.out.println(t)); // IllegalStateException:stream已经被操作或关闭

map

方法签名:<R> Stream<R> map(Function<? super T, ? extends R> mapper)
作用:将原Stream中的所有元素类型从T转化为R(不是强转,是通过一些操作得到R类型),返回封装R类型元素的Stream。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().map(t -> String.valueOf(t)) // 将元素都转换成String
    .forEach(t -> System.out.println(t)); // 输出:4 1 3

flatMap

方法签名:<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
作用:将原Stream中的所有元素类型从T转化为R,返回Stream<R>。与map的主要区别在于,适配一对多的数据类型。比如,类型T中存在一个List<String>,使用map返回一个Stream<List<String>>,而是用flatMap则返回一个Stream<String>,实现一对多的映射。

List<Integer[]> list = new ArrayList<>();
list.add(new Integer[] {1, 2, 3});
list.add(new Integer[] {4, 5, 3});
list.stream().flatMap(t -> Stream.of(t)) // 将各个数组中的元素都放入stream, 实现 1 → n 的转换
    .forEach(t -> System.out.println(t)); // 输出:1 2 3 4 5 3

终端操作

forEach

方法签名:void forEach(Consumer<? super T> action)
作用:迭代消费Stream里的所有元素,比如打印,写入文件,写入DB等。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
list.stream().forEach(t -> System.out.println(t)); // 输出:4 1 3

toArray

方法签名:Object[] toArray()
作用:将Stream中的所有元素封装成Object[]返回。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Object[] objects = list.stream().toArray(); // 与元素类型无关, 固定返回Object[]
Integer[] ints = (Integer[])objects; // 使用时你可能需要类型强转

count

方法签名:long count()
作用:返回Stream中的元素个数。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
long count = list.stream().count(); // 返回stream中的元素个数, 当前为3

findFirst

方法签名:Optional<T> findFirst()
作用:Stream的第一个元素封装在Optional中返回。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Optional<Integer> first = list.stream().findFirst(); // 返回stream中的第一个元素, 当前为4
/* 
	Optional类内容比较多所以现在不做赘述, 大家姑且就认为是个只能存放一个元素的容器就好,以后会开一个新的博文详细为	大家讲解用法
 */

anyMatch

方法签名:boolean anyMatch(Predicate<? super T> predicate)
作用:当存在符合predicate条件的元素时返回true,否则返回false。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
boolean bool = list.stream().anyMatch(t -> t <= 1); // 当一个元素小于等于1时就返回true, 当前true

allMatch

方法签名:boolean allMatch(Predicate<? super T> predicate)
作用:当所有元素都符合predicate条件时返回true,否则返回false。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
boolean bool = list.stream().allMatch(t -> t <= 1); // 当所有元素小于等于1时才返回true, 当前false

noneMatch

方法签名:boolean noneMatch(Predicate<? super T> predicate)
作用:当所有元素都不符合predicate条件时返回true,否则返回false。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
boolean bool = list.stream().noneMatch(t -> t <= 1); // 当所有元素都不小于等于1时才返回true, 当前false

min

方法签名:Optional<T> min(Comparator<? super T> comparator)
作用:按照传入的比较器将最小的元素封装在Optional中返回。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Optional<Integer> min = list.stream().min((o1, o2) -> o2 - o1); // 根据传入的比较器实现返回最小值, 当前4
/*
	这个方法可以认为stream按照传入的比较器排序, 返回排序后的第一个元素
 */

max

方法签名:Optional<T> max(Comparator<? super T> comparator)
作用:按照传入的比较器将最大的元素封装在Optional中返回。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Optional<Integer> max = list.stream().max((o1, o2) -> o2 - o1); // 根据传入的比较器实现返回最大值
/*
	这个方法可以认为stream按照传入的比较器排序, 返回排序后的最后一个元素
 */

collect

由于有两种重载,所有分开说。

方法签名:<R, A> R collect(Collector<? super T, A, R> collector)
作用:迭代消费Stream里的所有元素,返回一个R类型的容器。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Set<Integer> set = list.stream().collect(Collectors.toSet()); // 将所有元素封装成Set返回, 元素类型不变
/*
	由于Collector并不是一个函数式接口,所以我们在使用过程中必须传入一个实现类或者匿名内部类(不推荐,代码太长),而不能使用Lambda表达式的形式。
	而常用的Colloector的实现类中就有Collectors,这是个提供类型转换功能的类,可以将元素转换为各种Collection或Map。
 */

方法签名:<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
作用:开启多个线程,各线程分别累加,最后合并各线程结果。

推荐使用并行流调用本方法。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
List<Integer> collect = list.parallelStream() // 由于线程合并操作涉及到多线程所以使用并行流,否则将不执行线程合并操作
                .collect(() -> new ArrayList<Integer>(), // 创建一个ArrayList<Integer>对象, 每个线程都会执行
                (r, t) -> r.add(t), // 向上面创建的List里追加t(原本list中的元素), 每个线程都会执行
                (r1, r2) -> r1.addAll(r2)); // 将两个线程的List合并到一起,返回。
/*
	这里开启线程与合并线程结果的逻辑与归并排序非常类似
	由于原数组中有三个元素, 所以最终开启三个线程
	线程1: [4]
	线程2: [1]
	线程3: [3]
	接下来是线程合并:
	第一轮: 
		线程1: 线程1的结果 + 线程2的结果 -> [4]
		线程2: 线程2的结果 + 线程3的结果 -> [1, 3]
	第二轮: 
		线程1: 线程1的结果 + 线程2的结果 -> [4, 1, 3]
	返回线程1的结果
 */

在第二种重载中如果使用的是串行流则实际的执行流程与第一种重载相同并不会执行线程合并的操作,所以如果一定要用串行流则非常不建议使用第二种重载方法,使用第一种即可。

reduce

由于有三种重载,所有分开说。

与sql中的聚合函数比较相似,都是计算多个数据返回一个结果,实现 n → 1。

方法签名:T reduce(T identity, BinaryOperator<T> accumulator)
作用:初始值identity与所有的元素都计算一遍,返回最终结果T。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Integer sum = list.stream().reduce(10, (t1, t2) -> t1 + t2); // 在初始值10的基础上累加没有元素的值并返回, 当前18

方法签名:Optional<T> reduce(BinaryOperator<T> accumulator)
作用:无初始值,对所有的元素都计算一遍,将最终结果的T封装在Optional中返回。

List<Integer> list = new ArrayList<>();
list.add(4);
list.add(1);
list.add(3);
Optional<Integer> sum = list.stream().reduce((t1, t2) -> t1 + t2); // 返回所有元素的累加, 当前8

方法签名:<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
作用:开启多个线程,各线程分别累加,最后合并各线程结果。

推荐使用并行流调用本方法。

List<String> list = new ArrayList<>();
        list.add("4");
        list.add("1");
        list.add("5");
        list.add("2");
Integer sum = list.parallelStream() // 由于线程合并操作涉及到多线程所以使用并行流,否则将不执行线程合并操作
    .reduce(10, // 定义初始值, 此初始值各线程独立存在
            (u, t) -> u + Integer.valueOf(t), // u: 初始值, t: 原list的元素值
            (u1, u2) -> u1 + u2); // 合并各线程的结果, 返回, 当前52
/*
	这里开启线程与合并线程结果的逻辑与归并排序非常类似
	由于原数组中有四个元素, 所以最终开启四个线程
	线程1: 10 + 4 -> 14
	线程2: 10 + 1 -> 11
	线程3: 10 + 5 -> 15
	线程4: 10 + 2 -> 12
	上面这一步省略了各线程累加的过程, 实际就是在初始值的基础上累加
	接下来是线程合并:
	第一轮: 
		线程1: 线程1的结果 + 线程2的结果 -> 14 + 11 -> 25
		线程3: 线程3的结果 + 线程4的结果 -> 15 + 12 -> 27
	第二轮: 
		线程1: 线程1的结果 + 线程3的结果 -> 25 + 27 -> 52
	返回线程1的结果
 */

在第三种重载中如果使用的是串行流则实际的执行流程与第一种重载相同并不会执行线程合并的操作,所以如果一定要用串行流则非常不建议使用第三种重载方法,使用第一种即可。

对于Lambda的细节还不熟悉的同学请参考此链接: Java---Lambda

本文只讨论了串行流与并行流的基本使用方法,对于Stream的延迟处理、并发处理等进阶的内容请参考 Java---Stream进阶(已更新)

posted @ 2022-08-26 00:59  spoonb  阅读(304)  评论(0编辑  收藏  举报