流
从迭代到流
流的概念
原先在处理集合里面的数据的时候,通常大家会采取这样的形式。比如对集合中的字符串长度大于10的进行个数的统计。我们通常会这么写
for (String s : list){
if (s.length()>10){
sum++;
}
}
但是如果用流式编程的话,我们可以这么写。
long sum = list.stream().filter(item -> item.length() > 10).count();
优点:
从代码的角度来看,流式编程更加的直接。不需写一个遍历去查找和过滤的操作。方法名字就能过直接告诉我们,我们意欲何为。
流的工作流程
1.创建一个流:有两种方式,一种是stream,一种是parallelStream。这两种方式都可以创建出来一个流。
2.流的操作:将初始流转换为其他流的中间操作,例如filter里面的过滤条件。
3.终止条件:流结束的条件。例如count();
流的特性
1.流不存储数据
2.流不会去修改源数据源
3.流得操作尽可能的是惰性的。也就是说,在你需要结果的时候流才会执行。举个例子:就是在count之前,你可以写多个的filter。
流的操作filter,map,flatMap
流的转换会产生一个新的流,它的元素派生于另一个流中的元素。
filter
filter会转换一个流,他的元素与某种条件相匹配。
在上面的代码中,我们能够看到filter中的引元(就是用一个字母代表一个世子)是peidicate<T>。即从T到boolean的函数。
Map
转换原先流里面的元素。
如果我们想按照某一种方式去转换源流里面的值的时候,我们可以用map方法传递执行该转换的函数。例如,把所有单词转成小些。
words.stream.map(String::toLoweCase);
map中紧跟的就是转换流元素的条件。
flatMap
map函数的使用是应用在流中每一个值的身上,其返回结果呢,是这个应用函数之后所产生的结果。但是有时候这个返回结果不是单纯的就是一个值,而是包含众多元素的流。这时候就使用flatMap函数
抽取子流和连接流
其实就是悬着前几个元素和跳过前几个元素。
stream.limit(n)
不用多说,顾名思义就是截取前n个元素转换为一个新的流。
比如下面:输出100个随机数
Stream.generate(Math::random).limit(100);
stream.skip(n)
顾名思义就是跳过前面n个元素,再把后面的元素转换为一个流。
stream.concat
这个作用是用于连接两个流,就是把第一个流里面的元素,和第二个流里面的元素合并起来。这里有一个要注意的就是,流里面的值的类型要注意,还有就是第一个流不能是无限流,不然第二个流不回呗处理的。就跟死循环是一个道理。
Optional类型对象
其主要解决的问题是空指针问题。
Optional<T>对象是一种包装器对象。一般来说在流约简的时候的返回对象(比如:max,min 之类的)。包装器对象要么包装了T对象,要么什么都没有。Optional<T>类型被当做一种更加安全的方式,用来替代类型T的引用,这种引用要么引用了某个对象。要么是null,但是它只有在正确使用的情况下线才会更加安全。
如何有效的使用呢:在值不存在的时候产生一个可替代物,只有值存在的时候才允许使用这个值。
说了这么多讲的还是一个概念,我们来讲讲实际的东西。
在代码里面如果一个对象,我们这么写的话,那么会有出现空指针的情况。
user.getUserName()
那么一般来说会在操作之前,先判断对象是否是null
if(user!=null){
user.getUserName();
}
一个两个还行,数量一多if分支就多了起来,后期维护也很麻烦。
直接点,我们直接看Optional引入之后应该怎么写
Optional.ofNullable(user).orElse(user2);
这段代码的意思是,如果user为空,就返回备用方案user2,要是不为空那就返回原先的user
说到这里可能会觉得Optional只是给null的情况准备了一套备用方案而已。其实不然
我们再来说一下Optional的两个关键方法
ofNullable用来作为可能出现null值,和可选择值之间的桥梁。他会在值为null的时候返回一个空对象,不为bull的时候返回原先值
在举个例子
如果下面这一段代码,为了保证不出现null,会加了很多的判断。这样其实很不优雅
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
这样就行了
Optional.ofNullable(user)
.map(item1 -> item1.getAddress())
.map(item2 -> item2.getCountry())
.map(item3 -> item3.getIsocode())
.ofElse(XXXX);
看到这里是不是就明白了Optional出现的意义了呢。
这拜年顺便提一嘴
ofElser和ofElseGet的区别
区别在于:单判断的对象为空的时候,两个都会执行自己的备用方案。但是如果对象不为空的时候,ofElse还是会执行一遍自己的备用方案,但是ofElseGet不会。建议用ofElseGet
流收集结果
收集的话一般是用collect():将转换流收集到某个集合或者列表。
把这个拿出来说是因为这个collect有着很丰富的api。建议看一遍。这边就不一一列举。
流收集到映射表:toMap
作用就是可以理解为将list转换为Map。但是i这里说这个是因为又一点要注意。就是在转化过程中,要是出现多个key值相同的时候,如果是简单的写成下面这种情况的话,是会出现冲突的。
Collectors.toMap(User::getId,User::getName);
那么就要对出现冲突的现在做处理。比如,只取现存在的值。如下图(发现书本里面有,就不再写了)
并行流
故名思义,并行流的意思就是可以用来并行的处理流里面的数据。前面我们讲的都是顺序流。
在java7的时候,如果需要处理并行的处理一些数据的时候,可能我们需要把这些数据分为若干个,然后呢在分配不同的线程去执行。并且有时候需要其他条件的时候,还需要增加同步条件。
那么这时候呢有了并行流,它的作用就是把一个内容分成多个模块,并用不同的线程去处理它。
原理是什么呢?
并行流内部使用了默认的ForkJoinPool,它默认的线程数量就是你的处理器数量,这个值是由Runtime.getRuntime().avaliableProcessors()得到的
可以修改,但是不建议
工作原理:Fork/Join
就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务。拆到什么时候呢? 拆到不可再拆时,再将一个个的小任务运算的结果进行 join 汇总.
更加深层次原理请参考“工作窃取模式”。这边时间原因先不赘述。