输出结果:
为什么会返回两个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
- 函数功能:规约流
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
感受:
其实我投简历的时候,都不太敢投递阿里。因为在阿里一面前已经过了字节的三次面试,投阿里的简历一直没被捞,所以以为简历就挂了。
特别感谢一面的面试官捞了我,给了我机会,同时也认可我的努力和态度。对比我的面经和其他大佬的面经,自己真的是运气好。别人8成实力,我可能8成运气。所以对我而言,我要继续加倍努力,弥补自己技术上的不足,以及与科班大佬们基础上的差距。希望自己能继续保持学习的热情,继续努力走下去。
也祝愿各位同学,都能找到自己心动的offer。
分享我在这次面试前所做的准备(刷题复习资料以及一些大佬们的学习笔记和学习路线),都已经整理成了电子文档
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
感受:
其实我投简历的时候,都不太敢投递阿里。因为在阿里一面前已经过了字节的三次面试,投阿里的简历一直没被捞,所以以为简历就挂了。
特别感谢一面的面试官捞了我,给了我机会,同时也认可我的努力和态度。对比我的面经和其他大佬的面经,自己真的是运气好。别人8成实力,我可能8成运气。所以对我而言,我要继续加倍努力,弥补自己技术上的不足,以及与科班大佬们基础上的差距。希望自己能继续保持学习的热情,继续努力走下去。
也祝愿各位同学,都能找到自己心动的offer。
分享我在这次面试前所做的准备(刷题复习资料以及一些大佬们的学习笔记和学习路线),都已经整理成了电子文档
[外链图片转存中…(img-DuDs7Pno-1712041011305)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!