java中的流stream_java中的Stream流

本文详细介绍了Java8中的Stream API,如何通过Stream进行集合元素的高效处理,包括过滤、映射、计数等操作,强调了流式思想的工厂流水线模型,并展示了Stream如何替代传统循环实现更优雅的代码。通过Stream,开发者可以专注于业务逻辑,而非实现细节,提升代码的可读性和性能。
摘要由CSDN通过智能技术生成

java中的Stream流

说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。

一、引言

传统集合的多步遍历代码

几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元 素进行操作的时候,除了必需的添加、删除、获取外,典型的就是集合遍历。

循环遍历的弊端

Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行 了对比说明。现在,我们仔细体会一下上例代码,可以发现:

for循环的语法就是“怎么做”

for循环的循环体才是“做什么”

为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从 第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

试想一下,如果希望对集合中的元素进行筛选过滤:

将集合A根据条件一过滤为子集B;

然后再根据条件二过滤为子集C。

那怎么办?在Java 8之前的做法可能为:

public class Demo01List {

public static void main(String[] args) {

List list = new ArrayList<>();

list.add("张无忌");

list.add("周芷若");

list.add("赵敏");

list.add("张强");

list.add("张三丰");

//对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中

List listA = new ArrayList<>();

for (String s : list) {

if (s.startsWith("张")) {

listA.add(s);

}

}

//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中

List listB = new ArrayList<>();

for (String s : listA) {

if (s.length() == 3) {

listB.add(s);

}

}

//遍历listB集合

for (String s : listB) {

System.out.println(s);

}

}

}

这段代码中含有三个循环,每一个作用不同:

首先筛选所有姓张的人;

然后筛选名字有三个字的人;

后进行对结果进行打印输出。

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循 环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使 用另一个循环从头开始。

那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?

Stream的更优写法

下面来看一下借助Java8的Stream API,什么才叫优雅:

public class Demo02Stream {

public static void main(String[] args) {

//创建一个List集合,存储姓名

List list = new ArrayList<>();

list.add("张无忌");

list.add("周芷若");

list.add("赵敏");

list.add("张强");

list.add("张三丰");

//对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中

//对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中

//遍历listB集合

list.stream()

.filter(name -> name.startsWith("张"))

.filter(name -> name.length() == 3)

.forEach(name -> System.out.println(name));

}

}

直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码 中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。

二、流式思想概述

注意:请暂时忘记对传统IO流的固有印象!

整体来看,流式思想类似于工厂车间的“生产流水线”。

31f01624f85a4079cbd186f631a35f99.png

当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤 方案,然后再按照方案去执行它。

3fc9ac13a7a353219d5c8c5569a3fe0a.png

这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模 型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而右侧的数字 3是终结果。

这里的 filter 、 map 、 skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 count 执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。

备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何 元素(或其地址值)。

Stream(流)是一个来自数据源的元素队列

元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。

数据源 流的来源。 可以是集合,数组 等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。

内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭 代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。

当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结 果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以 像链条一样排列,变成一个管道。

三、获取流

java.util.stream.Stream 是Java 8新加入的常用的流接口。(这并不是一个函数式接口。)

获取一个流非常简单,有以下几种常用的方式:

所有的 Collection 集合都可以通过 stream 默认方法获取流;

Stream 接口的静态方法 of 可以获取数组对应的流。

根据Collection & Map & Array 获取流 &

首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。

java.util.Map 接口不是 Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流 需要分key、value或entry等情况:

如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单:

备注: of 方法的参数其实是一个可变参数,所以支持数组。

/*

java.util.stream.Stream是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)

获取一个流非常简单,有以下几种常用的方式:

- 所有的Collection集合都可以通过stream默认方法获取流;

default Stream stream​()

- Stream接口的静态方法of可以获取数组对应的流。

static Stream of​(T... values)

参数是一个可变参数,那么我们就可以传递一个数组

*/

public class Demo01GetStream {

public static void main(String[] args) {

//把集合转换为Stream流

List list = new ArrayList<>();

Stream stream1 = list.stream();

Set set = new HashSet<>();

Stream stream2 = set.stream();

Map map = new HashMap<>();

//获取键,存储到一个Set集合中

Set keySet = map.keySet();

Stream stream3 = keySet.stream();

//获取值,存储到一个Collection集合中

Collection values = map.values();

Stream stream4 = values.stream();

//获取键值对(键与值的映射关系 entrySet)

Set> entries = map.entrySet();

Stream> stream5 = entries.stream();

//把数组转换为Stream流

Stream stream6 = Stream.of(1, 2, 3, 4, 5);

//可变参数可以传递数组

Integer[] arr = {1,2,3,4,5};

Stream stream7 = Stream.of(arr);

String[] arr2 = {"a","bb","ccc"};

Stream stream8 = Stream.of(arr2);

}

}

四、常用方法

2078b09c9c121d6f21cf1c759e9f9567.png

流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方 法均为延迟方法。)

终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调 用。本小节中,终结方法包括 count 和 forEach 方法。

备注:本小节之外的更多方法,请自行参考API文档。

逐一处理:forEach

虽然方法名字叫 forEach ,但是与for循环中的“for-each”昵称不同。

void forEach(Consumer super T> action);

该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。

基本使用:

public class Demo02Stream_forEach {

public static void main(String[] args) {

//获取一个Stream流

Stream stream = Stream.of("张三", "李四", "王五", "赵六", "田七");

stream.forEach(name->System.out.println(name));

}

}

过滤:filter

可以通过 filter 方法将一个流转换成另一个子集流。方法签名:

Stream filter(Predicate super T> predicate);

该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件

fa868889aee1b3e853b9774d1c327e3e.png

基本使用:

/*

Stream流中的常用方法_filter:用于对Stream流中的数据进行过滤

Stream filter(Predicate super T> predicate);

filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤

Predicate中的抽象方法:

boolean test(T t);

*/

public class Demo03Stream_filter {

public static void main(String[] args) {

//创建一个Stream流

Stream stream = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");

//对Stream流中的元素进行过滤,只要姓张的人

Stream stream2 = stream.filter((String name)->{return name.startsWith("张");});

//遍历stream2流

stream2.forEach(name-> System.out.println(name));

/*

Stream流属于管道流,只能被消费(使用)一次

第一个Stream流调用完毕方法,数据就会流转到下一个Stream上

而这时第一个Stream流已经使用完毕,就会关闭了

所以第一个Stream流就不能再调用方法了

IllegalStateException: stream has already been operated upon or closed

*/

//遍历stream流

stream.forEach(name-> System.out.println(name));

}

}

映射:map

如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:

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

该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流,称为映射。

e96e43db94df52e75ea7faefaed69239.png

基本使用

public class Demo04Stream_map {

public static void main(String[] args) {

//获取一个String类型的Stream流

Stream stream = Stream.of("1", "2", "3", "4");

//使用map方法,把字符串类型的整数,转换(映射)为Integer类型的整数

Stream stream2 = stream.map((String s)->{

return Integer.parseInt(s);

});

//遍历Stream2流

stream2.forEach(i-> System.out.println(i));

}

}

统计个数:count

正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数:

long count();

该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。

基本使用:

public class Demo05Stream_count {

public static void main(String[] args) {

//获取一个Stream流

ArrayList list = new ArrayList<>();

list.add(1);

list.add(2);

list.add(3);

list.add(4);

list.add(5);

list.add(6);

list.add(7);

Stream stream = list.stream();

long count = stream.count();

System.out.println(count);//输出7

}

取用前几个:limit

`limit方法可以对流进行截取,只取用前n个,方法签名:

Stream limit(long maxSize);

基本使用:

public class Demo06Stream_limit {

public static void main(String[] args) {

//获取一个Stream流

String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};

Stream stream = Stream.of(arr);

//使用limit对Stream流中的元素进行截取,只要前3个元素

Stream stream2 = stream.limit(3);

//遍历stream2流

stream2.forEach(name-> System.out.println(name));

}

}

跳过前几个:skip

如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:

Stream skip(long n);

d45380b95c321b2c6da83be611cb7817.png

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。

基本使用:

public class Demo07Stream_skip {

public static void main(String[] args) {

//获取一个Stream流

String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};

Stream stream = Stream.of(arr);

//使用skip方法跳过前3个元素

Stream stream2 = stream.skip(3);

//遍历stream2流

stream2.forEach(name-> System.out.println(name));

}

}

// 灰太狼

// 红太狼

组合:concat

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :

public static Stream concat(Stream extends T> a, Stream extends T> b) {

Objects.requireNonNull(a);

Objects.requireNonNull(b);

@SuppressWarnings("unchecked")

Spliterator split = new Streams.ConcatSpliterator.OfRef<>(

(Spliterator) a.spliterator(), (Spliterator) b.spliterator());

Stream stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());

return stream.onClose(Streams.composedClose(a, b));

}

基本使用:

public class Demo08Stream_concat {

public static void main(String[] args) {

//创建一个Stream流

Stream stream1 = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");

//获取一个Stream流

String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};

Stream stream2 = Stream.of(arr);

//把以上两个流组合为一个流

Stream concat = Stream.concat(stream1, stream2);

//遍历concat流

concat.forEach(name-> System.out.println(name));

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值