[Java进阶篇][函数式编程][Java 8+ Stream API]

[Java进阶篇][函数式编程][Java 8+ Stream API]

序言

Java 8 以后 Java也引入了大量函数式编程的内容
从完全的命令式编程到加入一些函数式编程语言的特性

为了更好的帮助广大未接触过函数式编程的程序员使用这些特性
Java 8以后提供了Stream API
(虽然相比C# linq是差了不少 但对于广大长期停留在 1.5 1.6特性的老Javaer已经很好用了)

上篇文章介绍了Java8 以后Java引入了Lambda表达式
而Java8中的Stream与lambda表达式可以说是相伴相生的
通过Stream我们可以更友好的更为流畅更为语义化的操作集合
而操作的过程就要通过调用行为表达式(Lambada)来进行
这就更接近函数式编程 而不是传统的命令式

Stream api都位于java.util.stream包
其中就包含了最核心的Stream接口
一个Stream实例可以非常方便的以串行或者并行操作一组元素序列
大大降低了并发操作的难度

Stream 是什么?

Stream是一种可供流式操作的数据视图 有些类似数据库中视图的概念
它不改变源数据集合 如果对其进行改变的操作 它会返回
一个新的数据集合

所以很多人说 Stream的名字起的太不好了 容易和IO Stream搞混
不过木已成舟 我们要做的就是接受 并且尽可能的掌握

Stream具体有什么新特性 这类文章在网上也已经有很多
此处不再赘述
有需要的可以看看这些文章
Java Stream API入门篇

Java 除了基础Stream之外 还提供了三种其他数据类型的Stream

stream

Stream不能使用构造器创建 因为其数据源多来自集合类
输入输出流,数组等 所以Stream一般使用工具方法创建
而Java 8 以后对于 这类常用的数据集合 已经添加了Stream
的创建方法

比如Arrays工具类的 Arrays.Stream方法

public static Stream stream(T[] array)

比如Collection接口的 stream方法

default Stream stream()

Stream接口内置的工具方法

public static Stream of(T… values)

创建流

  • 通过Stream接口的工具方法创建
Stream<String> stringStream=Stream.of("Apple", "Banana", "Coconut", "Damson");

IntStream intStream=IntStream.of(1,3,5,7,9);

LongStream longStream=LongStream.of(2l,4l,6l,8l);

DoubleStream doubleStream=DoubleStream.of(10d,20d,30d,40d,50d);
  • 通过Arrays工具类的Arrays.Stream方法从数组上创建
        Stream<String> stringStream=Arrays.stream(new String[]{
  "Saber","Lancer","Archer"});
  • 通过Collection接口的stream方法从集合上创建
 Stream<String> stream=list.stream();

这种创建方式也是最常用的方式 因为绝大部分流式操作都是用于对集合的操作

操作流

stream的操作分为为两类,中间操作(intermediate operations)和结束操作(terminal operations)
所有的流操作会被组合到一个流式操作管道中,这点类似linux中的管道概念,将多个简单操作连接在一起组成一个功能强大的操作。一个流式操作管道首先会有一个数据源,这个数据源可能是数组、集合、生成器函数或是IO通道,流操作过程中并不会修改源中的数据;然后还有零个或多个中间操作,每个中间操作会将接收到的流转换成另一个流(比如filter);最后还有一个终止操作,会生成一个最终结果(比如sum)。流是一种惰性操作,所有对源数据的计算只在终止操作被初始化的时候才会执行。

总结下
1. 中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream,不会进行实际的数据变更
2. 结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数,也就是说减少了内存占用。计算完成之后stream就会失效。

迭代

forEach()方法

Stream中最常用的一个方法 就是 forEach()方法
方法签名

void forEach(Consumer< ? super T> action);

遍历集合 对每个元素执行 action动作

终结操作

这个方法可以在很多场合取代for循环

传统for循环

  List<String> list=...

  for (String s : list) {
            System.out.println(s);       
        }

使用forEach

        list.stream().forEach(x-> System.out.println(x));

相比for循环 forEach的最大优势是可以和其他Stream级联调用
形成一组联合的语义化的操作

forEach是一个终结操作 所以被调用后Stream就被消费掉了
如果想在完成一轮迭代后还可以使用这个流 那么可以使用peek方法

peek()方法

方法签名

Stream peek(Consumer< ? super T> action);
可以看到peek的入参和forEach一样 就是 返回不同 peek方法仍返回一个Stream
而forEach作为终结方法 无返回值

中间操作

List.stream().peek(x-> {
            System.out.println(x);
        }).collect(Collectors.toList());

可以在迭代输出一遍流内容后 继续收集流到新的列表中

要注意的是peek不能和count,findFirst,anyMatc等短路方法连用,和短路方法连用时
peek会不起作用

映射

map()方法

另一个较常见的操作就是map方法
方法签名

Stream map(Function< ? super T, ? extends R> mapper);

包装操作(mapper)方法
作用是 遍历集合 并对所有元素执行mapper包装操作
然后返回 操作的结果元素组成一个新的Stream

简单的说,就是对每个元素按照某种操作进行转换,
转换前后Stream中元素的个数不会改变,
但元素的类型取决于转换之后的类型。

和forEach看起来挺相似的
但map的优势是其返回Stream
所以可以继续级联调用其他操作

中间操作

示例

        创建一个新String
        List<String> stringlist=list.stream()
                        .map(x->x.toUpperCase()).collect(Collectors.toList());

        级联调用forEach()方法
        list.stream().map(x->x.toUpperCase()).forEach(x-> System.out.println(x));
flatMap()方法

平铺并包装的方法
方法签名

Stream flatMap(Function< ? super T, ? extends Stream< ? extends R>> mapper)

作用是 可以把集合中的复杂结构数据 拆分成单个数据 便于继续处理
类似把原stream中的所有元素都”摊平”之后组成的Stream,转换前后元素的个数和类型都可能会改变。

中间操作

示例

        List<String> fruitList=Stream
                .of("apple","banana","coconut","Damson","Filbert","Lemon")
                .collect(Collectors.toList());

        List<String> meatList=Stream
                .of("beef","pork","chicken","lamp","oyster","salmon","tuna")
                .collect(Collectors.toList());


        List<List<String>> bigList=Stream.of(fruitList,meatList).collect(Collectors.toList());

        bigList.stream().flatMap(x->x.stream()).forEach(System.out::println);

输出结果

apple
banana
coconut
Damson
Filbert
Lemon
beef
pork
chicken
lamp
oyster
salmon
tuna

平铺后进行深层操作

bigList.stream().flatMap(x->x.stream().map(y->"eat "+y)).forEach(System.out::println);

结果

eat apple
eat banana
eat coconut
eat Damson
eat Filbert
eat Lemon
eat beef
eat pork
eat chicken
eat lamp
eat oyster
eat salmon
eat tuna

当然这样的操作和一下代码是等效的

   bigList.stream().flatMap(x->x.stream()).map(y->"eat "+y).forEach(System.out::println);

除了对应Stream的flatMap()方法外
Java还提供了对应其他几个流的方法

方法 原始流 转换流
flatMapToInt Stream IntStream
flatMapToLong Stream LongStream
flatMapToDouble Stream DoubleStream

过滤

filter()方法

过滤方法 方法签名

Stream filter(Predicate< ? super T> predicate);

入参是一个断言式函数接口
此方法的作用是 遍历集合 对集合中的每一个参数执行判断操作
符合的元素 加入到结果集中 并返回包含结果集的新Stream

中间操作

  List<String> list=Stream.of("apple","banana","coconut","Damson","Filbert","Lemon")
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值