前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java8-Stream Api流详解

java8-Stream Api流详解

作者头像
简熵
发布2023-03-06 21:46:52
6140
发布2023-03-06 21:46:52
举报
文章被收录于专栏:逆熵逆熵

java8-Stream Api流详解

1 Stream API的意义

流的定义:通俗地说,不用再写循环,判断等细节实现的代码,直接以声明式方式编写你的意图。节省了大量的中间容器去存储临时的中间变量。那流怎么来的呢,有这么一个定义:从支持数据处理操作的源生成的元素序列,流处理的是计算,集合处理的是存储

流的特点

  1. 流水线:流->流->流,流的结果返回一个新的流,多个操作串联起来就可以达到流水线的效果。
  2. 内部迭代:Collection API for-each属于外部迭代,作用是为了存储和访问数据。Stream API属于内部迭代,完全是在API库内部进行的数据处理;主要是为了描述对数据的计算。

示例

代码语言:javascript
复制
public static List<String> streamTreatment(List<Order> orders){
    return orders.stream()
        // 过滤金额大于100的元素
        .filter(o -> o.getAmount() > 100)
        //根据订单事件倒序
        .sorted(comparing(Dish::getCreated).reversed())
        //将订单信息转换成其他元素,通过lambda表达式提取购买人姓名
        .map(Order::getPayer)
        //截断流,指定元素个数不超过0
        .limit(10)
        //将流转换成集合List,汇总结果
        .collect(toList());
}
2 流和集合的关系

相同点:

  1. 拥有有序迭代的接口,顺序获取元素。
  2. 只能遍历一次

不同点:

  1. 二者计算的时间点不同。集合中的数据是计算完成的才能加入集合,可以删除和新增;流中的元素来自于源,不能删除和新增,流的元素是实时按照用户的需求计算产生的,延迟了最终的集合创建的时间。
  2. 迭代方式不同。集合中提供的迭代接口属于外部迭代。需要开发人员自己for循环并处理其中具体的一些操作;流的迭代属于内部迭代,在Stream库内部做了迭代,并且接受一些操作指令帮你把活干了。特别是关于并行,如果是外部迭代,开发人员必须自己做各种并行控制,而Stream流内部就天然做了并行化的支持,透明地实现并行。
3 流的操作
3.1 初级玩法
  • filter,map,limit,这种可以连成一条流水线的流操作,叫做中间操作,这种中间操作不会执行产生任何处理,在终端处操作触发的时候一次性处理。
  • collection这种可以触发流水线执行并关闭它,叫做终端操作。终端操作会从流的流水线生成结果。
  • 使用步骤:
    1. 一个数据源来执行查询
    2. 一串中间操作链,形成流的流水线
    3. 一个终端操作,执行流水线,并生成结果

示例

代码语言:javascript
复制
//流式操作链
List<String> strs = Arrays.asList("12345", "123", "123456", "1234567","12345678");
strs.stream()
    .filter(s -> s.length() > 3)
    .mapToInt(Integer::parseInt)
    .limit(3)
    .forEach(System.out::println);


//观察一下流式操作链中的执行顺序
strs.stream()
    .filter(s -> {
        System.out.println("filter--->"+s);
        return s.length() > 3;
    })
    .mapToInt(s->{
        System.out.println("mapToInt--->"+s);
        return Integer.parseInt(s);
    })
    .limit(3)
    .forEach(s->{
        System.out.println("forEach-->"+s);
    });
//执行结果 从结果可以知道,filter和mapToInt以及forEach是成双成对出现的,我们可以认为他们合并到了一次遍历中;并且整个集合中满足条件的不止3个,但根据结果只做了三次遍历打印,说明limit做了类似短路的操作。
/**
    filter--->12345
    mapToInt--->12345
    forEach-->12345
    filter--->123
    filter--->123456
    mapToInt--->123456
    forEach-->123456
    filter--->1234567
    mapToInt--->1234567
    forEach-->1234567
**/
3.2 花式玩法
3.2.1 筛选和切片
  • filter(T->boolean)操作,接受一个Predicate接口,并返回所有符合条件的元素的流。
  • distinct()去重操作,返回一个元素各异的流。主要通过集合中对象的hashCode和equals方法来判断是否相等。
  • limit(n)截短操作,返回一个不超过给定长度的流。流有序的话,会顺序返回前N个元素,否则随机返回。
  • skip(n)跳过操作,跳过前n个元素的流,如果元素为空,则返回一个空流。
3.2.2 映射
  • map(T->R)映射操作,将流中的每一个元素映射成一个新元素,转换成另一个对象。 ints.stream() .filter(i -> { System.out.println("filter--->"+i); return i> 3; }) .map(i->{ System.out.println("map--->"+i); return i*2; }) .limit(3) .distinct() .skip(1) .forEach(s->{ System.out.println("forEach-->"+s); });
  • flatMap(T->R)扁平化操作,将流中的每一个值(特别是数组或者集合种的元素)转换成一个个流,并合并成一个流,多层嵌套瞬间拍平。 //flatMap的作用就是将流 List<Integer> ints = Arrays.asList(1,2,3); List<Integer> ints2 = Arrays.asList(3,4); final List<ArrayList> collect1 = ints.stream() .flatMap(int1 -> ints2.stream() .map(int2 -> new ArrayList() { { add(int1); add(int2); } })).collect(toList()); System.out.println(collect1); // 其中的flatMap如果换成map操作的话,那么整个流式计算的结果就会是Stream<ArrayList> final List<Stream<ArrayList>> collect2 = ints.stream() .map(int1 -> ints2.stream() .map(int2 -> new ArrayList() { { add(int1); add(int2); } })).collect(toList()); final List<String> collect = strs.stream() .map(s -> s.split("")) .flatMap(Arrays::stream) .distinct() .collect(toList());
3.2.3 匹配&查找
  • anyMatch(Tall->boolean)匹配流中是否有一个符合要求的元素,如果存在返回true,这是一个终端操作。
  • allMatch(T->boolean)匹配流中所有元素是否都匹配条件,如果全部满足条件返回true,这是一个终端操作。
  • noneMatch(T->boolean)匹配流中所有元素是否都匹配条件,如果全部满足条件返回true,这是一个终端操作。
  • findAny(),结合filter使用,一旦找到任意一个就直接返回,返回值是Option<T>
  • Option<T>是一种容器类,代表一个值存在还是不存在,这样子就把臭名昭著的Null给包了起来。
  • findFrist(),结合filter使用,对于一些顺序的流,直接找到第一个就返回,返回值是Option<T>。 final Optional<String> any = strs.stream() .filter(s -> s.length() > 3) .findAny(); System.out.println(any); final Optional<String> first = strs.stream() .filter(s -> s.length() > 3) .findFirst(); System.out.println(first);
3.2.4 归约

定义:将流中的所有元素结合计算得到一个值,将流归约成一个值。

  • reduce(T,T,U->R),传入一个初始值,传入一个计算表达式 //元素求和 List<Integer> ints = Arrays.asList(1,2,3); final Integer reduce = ints.stream().reduce(2, (a, b) -> a + b); System.out.println(reduce); //考虑如果流中没有元素的话,用optional可以包裹一下 final Optional<Integer> reduce = ints.stream().reduce((a, b) -> a + b); System.out.println(reduce); //元素求最大值和最小值 final Optional<Integer> reduce2 = ints.stream().reduce(Integer::max); System.out.println(reduce2); final Optional<Integer> reduce2 = ints.stream().reduce(Integer::min); System.out.println(reduce2); //map-reduce求流中的元素个数 final Optional<Integer> reduce3 = ints.stream().map(i->1).reduce(Integer::sum); System.out.println(reduce3); //内部api 求流中的元素个数 long count= ints.stream().count(); System.out.println(count);
3.2.5 流操作的状态

有状态:比如filtermap,从输入流中获取每一个元素并在输出流中输出0或者1个结果,这种没有内部状态。这种就叫做无状态操作

无状态:比如reduce,max,min,需要内部状态来累积结果,但是内部的状态是有界的;再比如sort,distinct操作属于中间操作会输出新的流,并且内部状态是无界的,因为需要将中间计算的元素放在内存中存储,给后来的计算使用。这种就叫做有状态操作

操作

类型

返回类型

使用的类型/函数式接口

函数描述符

filter

中间

Stream<T>

Predicate<T>

T->boolean

distinct(有状态-无界)

中间

Stream<T>

skip(有状态-有界)

中间

Stream<T>

long

limit(有状态-有界)

中间

Stream<T>

long

map

中间

Stream<R>

Function<T,R>

T->R

flatMap

中间

Stream<R>

Function<T,Stream<R>>

T->Stream<R>

sorted(有状态-无界)

中间

Stream<T>

Comparator<T>

(T,T)->int

anyMatch

终端

boolean

Predicate<T>

T->boolean

noneMatch

终端

boolean

Predicate<T>

T->boolean

allMatch

终端

boolean

Predicate<T>

T->boolean

findAny

终端

Optional<T>

findFirst

终端

Optional<T>

forEach

终端

void

Consumer<T>

T->void

collect

终端

R

Collector<T,A,R>

reduce(有状态-有界)

终端

Optional<T>

BinaryOperator<T>

(T,T)->T

count

终端

long

3.2.6 数值流

为了减少装箱拆箱的损耗,提供了mapToInt,mapToDouble,mapToLong三个方法转换成IntStream,DoubleStream,LongStream,并且这些数值流还提供了很多方便的计算方法。

代码语言:javascript
复制
final IntStream intStream = strs.stream().mapToInt(s -> Integer.parseInt(s));
System.out.println(intStream.sum());
//转换回对象流
final Stream<Integer> boxed = intStream.boxed();
final DoubleStream doubleStream = strs1.stream().mapToDouble(s -> Double.parseDouble(s));
System.out.println(doubleStream.sum());
//转换回对象流
final Stream<Double> boxed1 = doubleStream.boxed();

final LongStream longStream = strs2.stream().mapToLong(s -> Long.parseLong(s));
System.out.println(longStream.sum());
//数值流Optional
final OptionalLong max = longStream.max();
//获取最大值,如果不存在就用默认值10
 final long l = max.orElse(10);
//转换回对象流
final Stream<Long> boxed2 = longStream.boxed();

示例:生成勾股定理的流

代码语言:javascript
复制
final Stream<int[]> stream1 = IntStream.rangeClosed(1, 100).boxed()
    .flatMap(a -> IntStream.rangeClosed(a, 100)
             .filter(b -> Math.sqrt(a * a + b * b) % 1 == 0)
             .mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)})
            );
stream1.forEach(arr-> System.out.println(arr[0]+"---"+arr[1]+"-----"+arr[2]));
/**
3---4-----5
5---12-----13
6---8-----10
7---24-----25
...
**/
3.3 生成流的几种做法
  1. 根据值生成流 // Stream.of Stream<String> stream = Stream.of("one", "two", "three", "four"); stream.map(String::toUpperCase).forEach(System.out::println);
  2. 根据数组生成流 int[] numbers = {2, 3, 5, 7, 11, 13}; System.out.println(Arrays.stream(numbers).sum());
  3. 根据文件流生成流 long uniqueWords = Files.lines(Paths.get("data.txt"), Charset.defaultCharset()) .flatMap(line -> Arrays.stream(line.split(" "))) .distinct() .count(); System.out.println("There are " + uniqueWords + " unique words in data.txt");
  4. 根据函数生成流(无限流) // 生成斐波那契数列数列,如果不用截断,那这个流会无限生成下去,接受初始化值和T->T Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1],t[0] + t[1]}) .limit(10) .forEach(t -> System.out.println("(" + t[0] + ", " + t[1] + ")")); // 生成无限流,和iterate的区别是它接受的是()->T Stream.generate(Math::random) .limit(10) .forEach(System.out::println);
4 高级归约

收集器Collector对流中的元素触发一个归约操作,最常见的一个恒等转换stream.collect(Collectors.toList()),其中借助了Collectors中的静态工厂方法。

4.1 现成的收集器

常见的一些归约汇总的代码如下:

代码语言:javascript
复制
// 累加数量
final Long collect = bookCorner.stream().collect(counting());
System.out.println(collect);
// 取流中元素 排序之后的最大值
final Optional<Book> collect1 = bookCorner.stream().collect(maxBy(Comparator.comparingDouble(Book::getPrice)));
if (collect1.isPresent()){
    System.out.println(collect1.get());
}

// 取流中元素 排序之后的最小值
final Optional<Book> collect2 = bookCorner.stream().collect(minBy(Comparator.comparingDouble(Book::getPrice)));
if (collect1.isPresent()){
    System.out.println(collect2.get());
}
// 取平均数
final Double collect3 = bookCorner.stream().collect(averagingDouble(Book::getPrice));
System.out.println(collect3);
// 累加求和
final Double collect4 = bookCorner.stream().collect(summingDouble(Book::getPrice));
System.out.println(collect4);
//字符串拼接
final String collect5 = bookCorner.stream().map(Book::getName).collect(joining(", "));
System.out.println(collect5);
4.2 reducing也能做这些事儿

除了使用现成的一些收集器方法之外,Collectors.reducing也能做到归约和汇总。接下来实现一下上文的几种便捷操作的几种替代写法。

代码语言:javascript
复制
// 累加数量
final Integer collect6 = bookCorner.stream().collect(reducing(0, price -> 1, (i, j) -> i + j));
System.out.println(collect6);
// 取流中元素 排序之后的最大值
final Optional<Double> collect7 = bookCorner.stream().map(Book::getPrice).collect(reducing((p1, p2) -> p1 - p2 > 0 ? p1 : p2));
if (collect7.isPresent()){
    System.out.println(collect7.get());
}

// 取流中元素 排序之后的最小值
final Optional<Double> collect8 = bookCorner.stream().map(Book::getPrice).collect(reducing((p1, p2) -> p1 - p2 > 0 ? p2 : p1));
if (collect8.isPresent()){
    System.out.println(collect8.get());
}
// 取平均数
// 取平均数
final Double collect3 = bookCorner.stream().collect(averagingDouble(Book::getPrice));
System.out.println(collect3);
// 累加求和
final Double collect9 = bookCorner.stream().collect(reducing(0d,Book::getPrice,Double::sum));
System.out.println(collect9);
//字符串拼接

final String collect10 = bookCorner.stream().collect(reducing("", Book::getName, (s1, s2) -> s1.concat(s2)));
System.out.println(collect10);
4.3 分组操作

使用Collectors.grouping来进行分组操作,入参中的Function就是分组函数,通过他去提取分组依据,并将流中的元素分成不同的组,返回值是<K,List<V>>,K为分组函数的返回值,List中装的是各自满足要求的元素。

代码语言:javascript
复制
final Map<Book.Type, List<Book>> collect11 = bookCorner.stream().collect(groupingBy(Book::getType));
System.out.println(collect11);

还可以实现多级分组,通过groupingBy中的另一个参数可以选择二级分组的规则,并且最终会嵌套在一级分组之内

代码语言:javascript
复制
final Map<Book.Type, Map<String, List<Book>>> collect12 = bookCorner.stream()
    .collect(groupingBy(Book::getType, groupingBy(book -> {
        if (book.getPrice() > 60d) return "Expensive";
        else return "cheap";
    })));
System.out.println(collect12);

通过groupingBy中的另一个参数定制化分组的返回值V,就像sql语句中的select count(field) from table group field

代码语言:javascript
复制
//返回每组的数量
final Map<Book.Type, Long> collect14 = bookCorner.stream().collect(groupingBy(Book::getType, counting()));
System.out.println(collect14);
//每组最大值
/**
{MATH=Optional[book2], CHINESE=Optional[book1], ENGLISH=Optional[book3]}

**/
final Map<Book.Type, Optional<Book>> collect15 = bookCorner.stream().collect(groupingBy(Book::getType, maxBy(Comparator.comparingDouble(Book::getPrice))));
System.out.println(collect15);
//通过collectingAndThen将收集器的结果转化为另一种类型
/**
{MATH=book2, CHINESE=book1, ENGLISH=book3}
**/

final Map<Book.Type, Book> collect16 = bookCorner.stream()
    .collect(groupingBy(Book::getType,
                        collectingAndThen(maxBy(Comparator.comparingDouble(Book::getPrice)), Optional::get)));
System.out.println(collect16);

配合mapping收集器玩一些小花样,mapping(Function,Collector)中Function用来对结果进行转换的函数,Collector将变换的结果收集起来。

代码语言:javascript
复制
//{MATH=[cheap, Expensive], CHINESE=[cheap, Expensive], ENGLISH=[cheap, Expensive]}
final Map<Book.Type, Set<String>> collect17 = bookCorner.stream()
    .collect(groupingBy(Book::getType, mapping(book -> {
        if (book.getPrice() > 60d) return "Expensive";
        else return "cheap";
    }, toSet())));

System.out.println(collect17);
4.4 分区-特殊的分组

一种特殊的分组,partitioningBy(T->boolean),分区函数返回的是布尔值,所以最终的结果就是Map<Boolean,List<T>>

代码语言:javascript
复制
// {false=[book6], true=[book1, book2, book3, book4, book5, book7, book8, book9]}
final Map<Boolean, List<Book>> collect = bookCorner.stream().collect(partitioningBy(book -> book.getPrice() > 30));
System.out.println(collect);

partitioningBy同样也可以和groupingBy配合一起使用,通过groupingBy收集器会在Boolean作为key的Map中继续生成一个二级map

代码语言:javascript
复制
//{false={MATH=[book6]}, true={ENGLISH=[book3, book4, book9], CHINESE=[book1, book5, book7, book8], MATH=[book2]}}

final Map<Boolean, Map<Book.Type, List<Book>>> collect1 = bookCorner.stream().collect(partitioningBy(book -> book.getPrice() > 30, groupingBy(Book::getType)));
System.out.println(collect1);

综上,总结一下,现有的一些收集器类型:

工厂方法

返回类型

用于

tolist使用示例:ListBookes = menuStream.collect(toList());

List

把流中所有项目收集到一个List

toSet使用示例:SetBookes = menuStream.collect(toSet());

Set

把流中所有项目收集到一个set,删除重复项

toCollection使用示例:CollectionBookes=menuStream.collect(toCollection(),ArrayList::new);

Collection

把流中所有项目收集到给定的供应源创建的集合

counting使用示例:long howManyBookes = menuStream.collect(counting());

Long

计算流中元素的个数

summingInt示例:int totalPrices = menuStream.collect(summingInt(Book::getPrice));

Integer

对流中项目的一个整数属性求和

averagingInt使用示例:double avgPrices = menuStream.collect(averagingInt(Book::getPrice));

Double

计算流中项目Integer属性的平均值

summarizingInt使用示例:IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Book:getPrice));

IntSummaryStatistics

收集关于流中项目Inteaer属性的统计值,例如最大、最小、总和与平均值

joining使用示例:String shortMenu = menuStream.map(Book ::getName).collect(joining(","));

String

连接对流中每个项目调用toString 方法所生成的字符串

maxBy使用示例:Optional<Book> fattest = menuStream.collect(maxBy(comparinqInt(Book::getPrice)));

Optional<T>

一个包裹了流中按照给定比较器选出的最大元素的Optional.或如果流为空则为Optional.empty()

minBy使用示例:Optional<Book> lightest = menuStream.collect(minBy(comparingInt(Book::getPrice)));

Optional<T>

一个包裹了流中按照给定比较器选出的最小元素的Optional或如果流为空则为Optional.empty()

reducing使用示例:int totalPrices = menuStream.collect(reducing(0,Book::getPrice,Integer::sum));

归约操作产生的类型

从一个作为累加器的初始值开始,利用BinaryOperator与流中的元素逐个结合,从而将流归约为单个值

collectingAndThen使用示例:int howManyBookes = menuStream.collect(collectingAndThen(toList(),List::size));

转换函数返回的类型

包裹另一个收集器,对其结果应用转换函数

groupingBy使用示例:Map> BookesByType = menuStream.collect(groupingBy(Book::getType));

Map<K,List<T>>

根据项目的一个属性的值对流中的项目作问组,并将属性值作为结果Map的键

partitioningBy使用示例:Map<Boolean.List<Book> englishBookes =menuStream.collect(partitioningBy(Book::isEnglish));

Map<Boolean,List<T>>

根据对流中每个项目应用谓词的结果来对项目进行分区

5 Collector接口
代码语言:javascript
复制
public interface Collector<T, A, R> {
    Supplier<A> supplier();
    BiConsumer<A, T> accumulator();
    BinaryOperator<A> combiner(); 
    Function<A, R> finisher();
    Set<Characteristics> characteristics();
}

已上各个泛型代表的含义如下:

  • T代表流中要收集的项目的泛型
  • A是累加器的类型,累加器是在收集过程中用于累积部分结果的对象
  • R是收集操作得到的对象的类型
  • 比如toList(),他构造的收集器就如下所示 Collector<T, ?, List<T>> toList() { return new CollectorImpl<>(ArrayList::new, List::add, (left, right) -> { left.addAll(right); return left; }, CH_ID); }

再来看看接口中的五个方法

  1. 建立新的结果容器,supplier():返回值是一个()->T,调用时会创建一个空的累加器实例。
  2. 将元素添加到结果容器,accumulator():返回值是一个(T,U)->void,方法返回执行归约操作的函数,假设流中的元素有n个,当遍历到流中的第n个元素时,这个函数需要保存归约结果的累加器(已经收集了前n-1个项目)以及第n元素本身俩个参数去执行。
  3. 对结果容器应用最终转换,finisher():返回值是一个T->R,方法返回累积过程中最后要调用的函数。当流中所有的元素都遍历完了之后,通过该函数将累加器对象转换为整个集合操作最终的结果。
  4. 合并两个结果容器,combiner():返回值是一个(T,T)->T,调用时会返回一个供归约操作使用的函数。定义了对流的各个子部分进行并行处理时,各个子部分归约所得的累加器要如何合并。
  5. 定义收集器的行为,characteristics():返回一个不可变的Characteristics集合。定义了收集器的行为——尤其是关于流是否可以并行归约,以及可以使用哪些优化的提示。Characteristics是一个包含三个项目的枚举。
    • UNORDERED——归约结果不受流中项目的遍历和累积顺序的影响。
    • CONCURRENT——accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,那它仅在用于无序数据源时才可以并行归约。
    • IDENTITY_FINISH——这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器A不加检查地转换为结果R是安全的。
本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-12-21,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 逆熵架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • java8-Stream Api流详解
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com