携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情
Stream API
Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据, Stream讲的是计算
前置知识
Lambda表达式:基本使用,四大函数式接口,方法引用
不会的同学可以阅读我的上一篇文章Lambda表达式
特点
- Stream 不会存储输入的元素,而是计算他们,存储需要特定的API实现
- Stream 不会改变源对象。而是会返回一个具有结果的新 Stream,意味着可以
(xx().xx().xx()) - Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行(如下,当执行终止操作时才会执行中间操作)。
操作流程
- 创建Stream 一个数据源(如:集合、数组),获取一个流
- 中间操作 一个中间操作链,对数据源的数据进行处理
- 终止操作
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
流的种类
Parallel Stream 并行流
并行流采用多线程Frok/Join框架来实现任务,在这种情况下,不能保证原子性的操作可能会造成与预期外的错误,慎用
Sequential Stream 顺序流(常用)
使用方式
创建Stream
-
创建Stram方式一:通过集合
default Stream<E> stream():返回一个顺序流
default Stream<E> parallelStream()返回一个并行流Stream<String> stream = list.stream(); //返回一个顺序流 Stream<String> parallelStream = list.parallelStream(); //返回一个并行流 -
创建Stream方式二:通过数组
static <T> Stream<T> stream(T[] array): 返回一个流
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)IntStream stream1 = Arrays.stream(arr);不必去记忆各种流的类名,比如IntStream,LongStream之流,,仅仅需要理解不同的基本类型或引用类型可以创建对应流即可,在IDEA中输入
Arrays.stream(arr).var再加上Tab键就能得到如上结果 -
创建Stream方式三:通过Stream的of()
public static <T> Stream<T> of(T... values)返回一个流Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5, 6); -
创建Stream方式四:创建无限流
迭代:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)//遍历前10位偶数 Stream.iterate(0,t->t+2).limit(10).forEach(System.out::println);生成:
public static<T> Stream<T> generate(Supplier<T> s)//生成10个随机数 Stream.generate(Math::random).limit(10).forEach(System.out::println);
中间操作
如上所说,中间操作只会等到终止操作执行时再执行,这被称为惰性求值,多个中间操作就组成了一个流,流水线的流
筛选与切片
//filter(Predicate p) 查询包含`j`的字符串
list.stream().filter(s->s.contains("j")).forEach(System.out::println);
System.out.println();
//limit(n) 只需要前n个全速
list.stream().limit(3).forEach(System.out::println);
System.out.println();
//skip(n) 跳过前n个元素
list.stream().skip(3).forEach(System.out::println);
System.out.println();
//distinct 通过hashcode()和equals()去除重复元素
list.stream().distinct().forEach(System.out::println);
映射
public void t3() {
//map(Function f) 转为大写字符串
list.stream().map(s -> s.toUpperCase(Locale.ROOT)).forEach(System.out::println);
//flatMap() 将流中的每个值都换成另一个流,然后把所有流连接成一个流。
list.stream().flatMap(this::StringToStream).forEach(System.out::println);
}
public Stream<Character> StringToStream(String s) {
List<Character> characters = new ArrayList<>();
for (Character c : s.toCharArray()) {
characters.add(c);
}
return characters.stream();
}
排序
//sorted 自然排序(升序)
Stream.of(1, 6, 4, 76, 8, 4, 2, 53, 8).sorted().forEach(System.out::println);
//sorted(Comparator com) 定制排序(适用于没有继承Comparable的对象等等)
Stream.of('a', 'c', 't', 'p').sorted(Character::compare).forEach(System.out::println);
终止操作
-
终止操作执行之后会生成对应的结果,结果可以是List,Integer或void等,具体看使用的终止操作API
-
流执行了终止操作后便不可继续使用
匹配与查找
int[] arr = new int[]{5, 7, 43, 7, 213, 3523};
//allMatch 元素是否全大于18
boolean b = Arrays.stream(arr).allMatch(n -> n > 18);
//anyMatch 是否有元素大于18
boolean b2 = Arrays.stream(arr).anyMatch(n -> n > 18);
//noneMatch 元素是否全小于18
boolean b3 =Arrays.stream(arr).noneMatch(n -> n > 18);
//findFirst() 返回第一个元素
int first = Arrays.stream(arr).findFirst().getAsInt();
//findAny() 返回任意一个元素与
int any = Arrays.stream(arr).findAny().getAsInt();
//count() 返回元素个数
long count = Arrays.stream(arr).count();
//max(Comparator com) 返回流中最大值
int max = Arrays.stream(arr).max().getAsInt();
//min(Comparator com) 返回流中最小值
int min = Arrays.stream(arr).min().getAsInt();
//reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
int sum = Arrays.stream(arr).reduce(0, Integer::sum);
System.out.println(sum);
规约
备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名
收集
Collectors 提供了很多静态方法用于将流收集为List,Set等,同时也可以计算流
Stream.of(1,2,3,4,5,6).collect(Collectors.toList());
Java11好像有一个ListOf(...)的API,这个方法用不到咯
Parallel并行流的注意点示范
比如我们现在有两个ArrayList<Integer>,分别是L1和L2,我们想要使用并行流遍历L1的元素并自增放入L2中,于是诞生了以下代码
ArrayList<Integer> L1 = (ArrayList<Integer>) Stream.generate(new Random()::nextInt).limit(1000).collect(Collectors.toList());
PrintStream pw = System.out;
L1.forEach(pw::println);
System.out.println(L1.size());
ArrayList<Integer> L2 = new ArrayList<>();
L1.stream().parallel().map(i->i++).forEach(L2::add);
System.out.println(L2.size());
输出结果如下:
...
276484816
-661750695
472724207
-1944164995
L1 SIZE:1000
L2 SIZE:928
再次运行:
出现以下问题的原因上面已经说过了,不能保证原子性的方法并行流内就可能会出现预料之外的结果,ArrayList::add不能保证原子性,源码如下
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ensureCapacityInternal方法计算当前list容量和是否扩容
为什么呢?
比如现在有两个线程A,B ,若同时对L2进行插入操作,同时执行ensureCapacityInternal方法得到结果可以插入,结果A先插入了之后B再插入,容量不够就报以上错误
需要详细了解的同学可以搜索Fork/Join的原理