java8实战读书笔记:初识Stream、流的基本操作(流计算)

我们来看一下Stream关于map方法的声明:

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

接受一个函数Function,其函数声明为:T -> R,接收一个T类型的对象,返回一个R类型的对象。

当然,java为了高效的处理基础数据类型(避免装箱、拆箱带来性能损耗)也定义了如下方法:

IntStream mapToInt(ToIntFunction<? super T> mapper)

LongStream mapToLong(ToLongFunction<? super T> mapper)

DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

思考题:对于字符数值[“Hello”,“World”] ,输出字符序列,并且去重。

第一次尝试:

public static void test_flat_map() {

String[] strArr = new String[] {“hello”, “world”};

List strList = Arrays.asList(strArr);

strList.stream().map( s -> s.split(“”))

.distinct().forEach(System.out::println);

}

输出结果:

在这里插入图片描述

为什么会返回两个String[]元素呢?因为map(s -> s.split()) 此时返回的流为Stream<String[]>,那我们是不是可以继续对该Steam[String[]],把String[]转换为字符流,其代码如下:

public static void test_flat_map() {

String[] strArr = new String[] {“hello”, “world”};

List strList = Arrays.asList(strArr);

strList.stream().map( s -> s.split(“”))

.map(Arrays::stream)

.distinct().forEach(System.out::println);

}

其返回结果:

在这里插入图片描述

还是不符合预期,其实原因也很好理解,再次经过map(Arrays:stream)后,返回的结果为 Stream<Stream< String>>,即包含两个元素,每一个元素为一个字符流,可以通过如下代码验证:

public static void test_flat_map() {

String[] strArr = new String[] {“hello”, “world”};

List strList = Arrays.asList(strArr);

strList.stream().map( s -> s.split(“”))

.map(Arrays::stream)

.forEach( (Stream s) -> {

System.out.println(“\n --start—”);

s.forEach(a -> System.out.print(a + " "));

System.out.println(“\n --end—”);

} );

}

综合上述分析,之所以不符合预期,主要是原数组中的两个字符,经过map后返回的是两个独立的流,那有什么方法将这两个流合并成一个流,然后再进行disinic去重呢?

答案当然是可以的,flatMap方法闪亮登场:先看代码和显示结果:

public static void test_flat_map() {

String[] strArr = new String[] {“hello”, “world”};

List strList = Arrays.asList(strArr);

strList.stream().map( s -> s.split(“”))

.flatMap(Arrays::stream)

.distinct().forEach( a -> System.out.print(a +" "));

}

其输出结果:

在这里插入图片描述

符合预期。一言以蔽之,flatMap可以把两个流合并成一个流进行操作。

2.3 查找和匹配

Stream API提供了allMatch、anyMatch、noneMatch、findFirst和findAny方法来实现对流中数据的匹配与查找。

2.3.1 allMatch

我们先看一下该方法的声明:

boolean allMatch(Predicate<? super T> predicate);

接收一个谓词函数(T->boolean),返回一个boolean值,是一个终端操作,用于判断流中的所有元素是否与Predicate相匹配,只要其中一个元素不复合,该表达式将返回false。

示例如下:例如存在这样一个List a,其中元素为 1,2,4,6,8。判断流中的元素是否都是偶数。

boolean result = a.stream().allMatch( a -> a % 2 == 0 ); // 将返回false。

2.3.2 anyMatch

该方法的函数声明如下:

boolean anyMatch(Predicate<? super T> predicate)

同样接收一个谓词Predicate( T -> boolean ),表示只要流中的元素至少一个匹配谓词,即返回真。

示例如下:例如存在这样一个List a,其中元素为 1,2,4,6,8。判断流中的元素是否包含偶数。

boolean result = a.stream().anyMatch( a -> a % 2 == 0 ); // 将返回true。

2.3.3 noneMatch

该方法的函数声明如下:

boolean noneMatch(Predicate<? super T> predicate);

同样接收一个谓词Predicate( T -> boolean ),表示只要流中的元素全部不匹配谓词表达式,则返回true。

示例如下:例如存在这样一个List a,其中元素为 2,4,6,8。判断流中的所有元素都不式奇数。

boolean result = a.stream().noneMatch( a -> a % 2 == 1 ); // 将返回true。

2.3.4 findFirst

查找流中的一个元素,其函数声明如下:

Optional findFirst();

返回流中的一个元素。其返回值为Optional,这是jdk8中引入的一个类,俗称值容器类,其主要左右是用来避免值空指针,一种更加优雅的方式来处理null。该类的具体使用将在下一篇详细介绍。

public static void test_find_first(List menu) {

Optional dish = menu.stream().findFirst();

// 这个方法表示,Optional中包含Dish对象,则执行里面的代码,否则什么事不干,是不是比判断是否为null更友好

dish.ifPresent(a -> System.out.println(a.getName()));

}

2.3.5 findAny

返回流中任意一个元素,其函数声明如下:

Optional findAny();

2.4 reduce

reduce归约,看过大数据的人用过会非常敏感,目前的java8的流操作是不是有点map-reduce的味道,归约,就是对流中所有的元素进行统计分析,归约成一个数值。

首先我们看一下reduce的函数说明:

T reduce(T identity, BinaryOperator accumulator)

  • T identity:累积器的初始值。

  • BinaryOperator< T> accumulator:累积函数。BinaryOperator< T> extend BiFunction<T, U, R>。BinaryOperator的函数式表示,接受两个T类型的入参,返回T类型的返回值。

Optional reduce(BinaryOperator accumulator);

可以理解为没有初始值的归约,如果流为空,则会返回空,故其返回值使用了Optional类来优雅处理null值。

U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator combiner);

首先,最后的返回值类型为U。

  • U identity:累积函数的初始值。

  • BiFunction<U, ? super T, U> accumulator:累积器函数,对流中的元素使用该累积器进行归约,在具体执行时accumulator.apply( identity, 第二个参数的类型不做限制 ),只要最终返回U即可。

  • BinaryOperator< U> combiner:组合器。对累积器的结果进行组合,因为归约reduce,java流计算内部使用了fork-join框架,会对流的中的元素使用并行累积,每个线程处理流中一部分数据,最后对结果进行组合,得出最终的值。

温馨提示:对流API的学习,一个最最重点的就是要掌握这些函数式编程接口,然后掌握如何使用Lambda表达式进行行为参数化(lambda表达当成参数传入到函数中)。

接下来我们举例来展示如何使用reduce。

示例1:对集合中的元素求和

List goodsNumber = Arrays.asList( 3, 5, 8, 4, 2, 13 );

java7之前的示例:

int sum = 0;

for(Integer i : goodsNumber) {

sum += i;// sum = sum + i;

}

System.out.println(“sum:” + sum);

求和运算符: c = a + b,也就是接受2个参数,返回一个值,并且这三个值的类型一致。

故我们可以使用T reduce(T identity, BinaryOperator< T> accumulator)来实现我们的需求:

public static void test_reduce() {

List goodsNumber = Arrays.asList( 3, 5, 8, 4, 2, 13 );

int sum = goodsNumber.stream().reduce(0, (a,b) -> a + b);

//这里也可以写成这样:

// int sum = goodsNumber.stream().reduce(0, Integer::sum);

System.out.println(sum);

}

不知大家是否只读(a,b)这两个参数的来源,其实第一个参数为初始值T identity,第二个参数为流中的元素。

那三个参数的reduce函数主要用在什么场景下呢?接下来还是用求和的例子来展示其使用场景。在java多线程编程模型中,引入了fork-join框架,就是对一个大的任务进行先拆解,用多线程分别并行执行,最终再两两进行合并,得出最终的结果。reduce函数的第三个函数,就是组合这个动作,下面给出并行执行的流式处理示例代码如下:

public static void test_reduce_combiner() {

/** 初始化待操作的流 */

List nums = new ArrayList<>();

int s = 0;

for(int i = 0; i < 200; i ++) {

nums.add(i);

s = s + i;

}

// 对流进行归并,求和,这里使用了流的并行执行版本 parallelStream,内部使用Fork-Join框架多线程并行执行,

// 关于流的内部高级特性,后续再进行深入,目前先以掌握其用法为主。

int sum2 = nums.parallelStream().reduce(0,Integer::sum, Integer::sum);

System.out.println(“和为:” + sum2);

// 下面给出上述版本的debug版本。

// 累积器执行的次数

AtomicInteger accumulatorCount = new AtomicInteger(0);

// 组合器执行的次数(其实就是内部并行度)

AtomicInteger combinerCount = new AtomicInteger(0);

int sum = nums.parallelStream().reduce(0,(a,b) -> {

accumulatorCount.incrementAndGet();

return a + b;

}, (c,d) -> {

combinerCount.incrementAndGet();

return c+d;

});

System.out.println(“accumulatorCount:” + accumulatorCount.get());

System.out.println(“combinerCountCount:” + combinerCount.get());

}

从结果上可以看出,执行了100次累积动作,但只进行了15次合并。

流的基本操作就介绍到这里,在此总结一下,目前接触到的流操作:

1、filter

  • 函数功能:过滤

  • 操作类型:中间操作

  • 返回类型:Stream

  • 函数式接口:Predicate

  • 函数描述符:T -> boolean

2、distinct

  • 函数功能:去重

  • 操作类型:中间操作

  • 返回类型:Stream

3、skip

  • 函数功能:跳过n个元素

  • 操作类型:中间操作

  • 返回类型:Stream

  • 接受参数:long

4、limit

  • 函数功能:截断流,值返回前n个元素的流

  • 操作类型:中间操作

  • 返回类型:Stream

  • 接受参数:long

5、map

  • 函数功能:映射

  • 操作类型:中间操作

  • 返回类型:Stream

  • 函数式接口:Function<T,R>

  • 函数描述符:T -> R

6、flatMap

  • 函数功能:扁平化流,将多个流合并成一个流

  • 操作类型:中间操作

  • 返回类型:Stream

  • 函数式接口:Function<T, Stream>

  • 函数描述符:T -> Stream

7、sorted

  • 函数功能:排序

  • 操作类型:中间操作

  • 返回类型:Stream

  • 函数式接口:Comparator

  • 函数描述符:(T,T) -> int

8、anyMatch

  • 函数功能:流中任意一个匹配则返回true

  • 操作类型:终端操作

  • 返回类型:boolean

  • 函数式接口:Predicate

  • 函数描述符:T -> boolean

9、allMatch

  • 函数功能:流中全部元素匹配则返回true

  • 操作类型:终端操作

  • 返回类型:boolean

  • 函数式接口:Predicate

  • 函数描述符:T -> boolean

10、 noneMatch

  • 函数功能:流中所有元素都不匹配则返回true

  • 操作类型:终端操作

  • 返回类型:boolean

  • 函数式接口:Predicate

  • 函数描述符:T -> boolean

11、findAny

  • 函数功能:从流中任意返回一个元素

  • 操作类型:终端操作

  • 返回类型:Optional

12、findFirst

  • 函数功能:返回流中第一个元素

  • 操作类型:终端操作

  • 返回类型:Optional

13、forEach

  • 函数功能:遍历流

  • 操作类型:终端操作

  • 返回类型:void

  • 函数式接口:Consumer

  • 函数描述符:T -> void

14、collect

  • 函数功能:将流进行转换

  • 操作类型:终端操作

  • 返回类型:R

  • 函数式接口:Collector<T,A,R>

15、reduce

  • 函数功能:规约流

  • 操作类型:终端操作

  • 返回类型:Optional

  • 函数式接口:BinaryOperator

  • 函数描述符:(T,T) -> T

16、count

  • 函数功能:返回流中总元素个数

  • 操作类型:终端操作

  • 返回类型:long

由于篇幅的原因,流的基本计算就介绍到这里了,下文还将重点介绍流的创建,数值流与Optional类的使用。


最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

16、count

  • 函数功能:返回流中总元素个数

  • 操作类型:终端操作

  • 返回类型:long

由于篇幅的原因,流的基本计算就介绍到这里了,下文还将重点介绍流的创建,数值流与Optional类的使用。


最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-Cw1yeOFv-1715710077169)]

[外链图片转存中…(img-1lQYumXP-1715710077169)]

[外链图片转存中…(img-56IisXKT-1715710077169)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值