写点什么

java8 实战读书笔记:初识 Stream、流的基本操作,nginx 架构原理

  • 2021 年 11 月 10 日
  • 本文字数:4696 字

    阅读完需:约 15 分钟

版本 3:List<String> dishNames = menu.stream().map(Dish::getName).collect(Collectors.toList());


文章的后续部分尽量使用最简洁的 lambda 表达式。


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


<R> Stream<R> 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<String> 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<String> 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<String> strList = Arrays.asList(strArr);


strList.stream().map( s -> s.split(""))


.map(Arrays::stream)


.forEach( (Stream<String> 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<String> 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<T> findFirst();


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


public static void test_find_first(List<Dish> menu) {


Optional<Dish> dish = menu.stream().findFirst();


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


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


}


2.3.5 findAny


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


Optional<T> findAny();


2.4 reduce


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


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


T reduce(T identity, BinaryOperator<T> accumulator)


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

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


Optional<T> reduce(BinaryOperator<T> accumulator);


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


<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> 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<Integer> 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<Integer> goodsNumber = Arrays.asList( 3, 5, 8, 4, 2, 13 );


int sum =


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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<Integer> 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 类的使用。




欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:


1、源码分析RocketMQ专栏(40篇+)


2、源码分析Sentinel专栏(12篇+)


3、源码分析Dubbo专栏(28篇+)


4、源码分析Mybatis专栏

评论

发布
暂无评论
java8实战读书笔记:初识Stream、流的基本操作,nginx架构原理