JAVA8给我带了什么——流的概念和收集器
到现在为止,笔者不敢给流下定义,从概念来讲他应该也是一种数据元素才是。可是在我们前面的代码例子中我们可以看到他更多的好像在表示他是一组处理数据的行为组合。这让笔者很难去理解他的定义。所以笔者不表态。各位同志自行理解吧。
在没有流以前,处理集合里面的数据一般都会用到显示的迭代器。用一下前面学生的例子吧。目标是获得学分大于5的前俩位同学。
package com.aomi; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static java.util.stream.Collectors.toList; public class Main { public static void main(String[] args) {
// TODO Auto-generated method stub List<Student> stus = getSources(); Iterator<Student> ite = stus.iterator(); List<String> names = new ArrayList<>();
int limit = ;
while (ite.hasNext() && limit > ) { Student stu = ite.next(); if (stu.getScore() > ) { names.add(stu.getName());
limit--;
}
} for (String name : names) {
System.out.println(name);
} } public static List<Student> getSources() {
List<Student> students = new ArrayList<>(); Student stu1 = new Student(); stu1.setName("lucy");
stu1.setSex();
stu1.setPhone("");
stu1.setScore(); Student stu2 = new Student();
stu2.setName("lin");
stu2.setSex();
stu2.setPhone("");
stu2.setScore(); Student stu3 = new Student();
stu3.setName("lili");
stu3.setSex();
stu3.setPhone("");
stu3.setScore(); Student stu4 = new Student(); stu4.setName("dark");
stu4.setSex();
stu4.setPhone("");
stu4.setScore(); students.add(stu1);
students.add(stu2);
students.add(stu3);
students.add(stu4); return students;
} }
如果用流的话是这样子的。
public static void main(String[] args) {
// TODO Auto-generated method stub List<Student> stus = getSources(); List<String> names = stus.stream()
.filter(st -> st.getScore() > )
.limit()
.map(st -> st.getName())
.collect(toList());
for (String name : names) {
System.out.println(name);
} }
把这俩段代码相比较主要是为了说明一个概念:以前做法都是在外部迭代,最为体现就是笔者在外面定义了一个集合names 。而流却什么也没有,现在我们应该能清楚感受到流是在内部迭代。也就是说流已经帮你做好了迭代。我们只要传入相关的函数就可以得到想要的结果。至于内部迭代的好处,笔者没有办法亲身的感受,唯一的感觉就是代码变的简单明了了。但是官方说Stream库为了我们在内部迭代里面做了很多优化和充公利用性能的操作。比如并行操作。所以笔者就听官方了。
事实上,在用流的过程中,我们用到很多方法函数。比如上面的limit方法,filter方法等。这个定义为流操作。但是不管是什么操作,你必须要有一个数据源吧。总结如下:
- 数据源:用于生成流的数据,比如集合。
- 流操作:类似于limit方法,filter方法。
流还有一种特点——部分流操作是没有执行的。一般都是在collect函数执行的时候,才开始执行个个函数。所以我们可以细分一下流操作:
- 数据源:用于生成流的数据,比如集合。
- 中间操作:类似于limit方法,filter方法。这些操作做变了一个操作链,有一点流水线的概念。
- 终端操作:执行上面的操作链。比如collect函数。
从上面的讲解我们就可以感觉流好像是先收集相关的目标操作,什么意思呢?就是先把要做的事情计划一下,最后一声令下执行。而下这个命令是collect函数。这一点跟.NET的Linq是很像的。同时记得他只能执行一次。也就是说这个流执行一次之后,就不可能在用了。
笔者列一下以前的用到的函数
forEach:终端
collect:终端
count:终端
limit:中间
filter:中间
map:中间
sorted:中间
到目前为止我们用到的流都是通过集合来建一个流。笔者对此从来没有讲过。现在笔者来讲些构建流的方式。
在stream库里面为我们提供了这样子一个方法——Stream.of
package com.aomi; import java.util.Optional;
import java.util.stream.Stream; public class Main { public static void main(String[] args) {
// TODO Auto-generated method stub Stream stream = Stream.of("I", "am", "aomi"); Optional<String> firstWord = stream.findFirst(); if(firstWord.isPresent())
{
System.out.println("第一个字:"+firstWord.get());
}
} }
运行结果:
去看一下of方法的代码。如下
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
说明我们可能指定一个类型来建一个流。上面可以修改为
Stream<String> stream = Stream.of("I", "am", "aomi");
findFirst函数用于表示返回第一个值。那就是可能数据源是一个空呢?所以他有可以会返回null。所以就是用一个叫Optional类的表示可以为空。这样子我们就可以用Optional类的方法进一步做安全性的操作。比如判断有没有值(isPresent())
笔者想要建一个int类型的数组流玩玩。为了方便笔者便试给一下上面的代码。却发现报错了。
如果我把int改为Integer呢?没有问题了。所以注意要用引用类型的。int类型对应为Integer类型。
package com.aomi; import java.util.Optional;
import java.util.stream.Stream; public class Main { public static void main(String[] args) {
// TODO Auto-generated method stub Stream<Integer> stream = Stream.of(1, 2, 9); Optional<Integer> firstWord = stream.findFirst(); if(firstWord.isPresent())
{
System.out.println("第一个字:"+firstWord.get());
} } }
运行结果:
那想要用int类型呢?什么办呢?改改
package com.aomi; import java.util.OptionalInt;
import java.util.stream.IntStream; public class Main { public static void main(String[] args) {
// TODO Auto-generated method stub IntStream stream = IntStream.of(1, 2, 9); OptionalInt firstWord = stream.findFirst(); if(firstWord.isPresent())
{
System.out.println("第一个字:"+firstWord.getAsInt());
} } }
运行结果:
我们以上面的例子来一个猜测:是不是Double类型,只要修改为DoubleStream就行呢?试试。
package com.aomi; import java.util.OptionalDouble;
import java.util.stream.DoubleStream; public class Main { public static void main(String[] args) {
// TODO Auto-generated method stub DoubleStream stream = DoubleStream.of(1.3, 2.3, 9.5); OptionalDouble firstWord = stream.findFirst(); if(firstWord.isPresent())
{
System.out.println("第一个字:"+firstWord.getAsDouble());
} } }
运行结果:
结果很明显,我们的猜测是对的。所以见意如果你操作的流是一个int或是double的话,请进可能的用XxxStream 来建流。这样子在流的过程中不用进行拆装和封装了。必竟这是要性能的。在看一下如果数据源是一个数组的情况我们如何生成流呢?
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix) {
return new CollectorImpl<>(
() -> new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add, StringJoiner::merge,
StringJoiner::toString, CH_NOID);
}
在看一个叫toList函数的代码。
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
我们发现他会共同的返回一个Collector类型。从上面我们就可以知道他的任务就是用去处理最后数据。我们把他定为收集器。让我们看一下收集器的接口代码吧;
public interface Collector<T, A, R> { Supplier<A> supplier(); BiConsumer<A, T> accumulator(); BinaryOperator<A> combiner(); Function<A, R> finisher(); Set<Characteristics> characteristics();
}
光看前面四个方法是不是有一点熟悉的感觉。想要说明这个五个方法的作用。就必须明白一个概念——并行归约。前面笔者讲过流是一个内部迭代,也说Stream库为我们做一个很多优化的事情。其中一个就是并行。他用到了JAVA 7引入的功能——分支/合并框架。也就是说流会以递归的方式拆分成很多子流,然后子流可以并行执行。最后在俩俩的子流的结果合并成一个最终结果。而这俩俩合并的行为就叫归约 。如图下。引用于《JAVA8实战》
我们必须根据图上的意思来走。子流的图里面会调用到Collector类的三个方法。
- supplier方法:用创建数据存储的地方。
- accumulator方法:用于子流执行过程的迭代工作。即是遍历每一项都会执行。所以可以这里做一些工作。
- finisher方法:返回最后的结果,你可以在这里进一步处理结果。
每一个子流结束这之后,就是俩俩合并。这个时候就要看流的机制图了。
- combiner方法:会传入每一个子流的结果过来,我们就可以在这里在做一些工作。
- finisher方法:返回最后的结果。同上面子流的一样子。
好像没有characteristics什么事情。不是这样子的。这个方法是用来说明当前这个流具备哪些优化。这样子执行流的时候,就可以很清楚的知道要以什么样子的方式执行了。比如并行。
他是一个enum类。值如下
- UNORDERED:这个表示执行过程中结果不受归约和遍历的影响
- CONCURRENT:表示可以多个线和调用accumulator方法。并且可以执行并行。当然前无序数据的才并行。除非收集器标了UNORDERED。
- IDENTITY_FINISH:表示这是一个恒等函数,就是做了结果也一样子。不用做了可以跳过了。
由了上面的讲说明,我们在来写一个自己的收集器吧——去除相同的单词
DistinctWordCollector类:
package com.aomi; import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector; public class DistinctWordCollector implements Collector<String, List<String>, List<String>> { @Override
public Supplier<List<String>> supplier() {
// TODO Auto-generated method stub
return () -> new ArrayList<String>();
} /**
* 子流的处理项的过程
*/
@Override
public BiConsumer<List<String>, String> accumulator() {
// TODO Auto-generated method stub
return (List<String> src, String val) -> { if (!src.contains(val)) {
src.add(val);
}
};
} /**
* 俩俩并合的执行函数
*/
@Override
public BinaryOperator<List<String>> combiner() {
// TODO Auto-generated method stub
return (List<String> src1, List<String> src2) -> {
for (String val : src2) {
if (!src1.contains(val)) {
src1.add(val);
}
}
return src1;
};
} @Override
public Function<List<String>, List<String>> finisher() {
// TODO Auto-generated method stub
return Function.identity();
} @Override
public Set<Characteristics> characteristics() {
// TODO Auto-generated method stub
return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT));
} }
Main:
public static void main(String[] args) {
// TODO Auto-generated method stub List<String> words = Arrays.asList("aomi","lili","lucy","aomi","Nono"); List<String> vals = words.stream().collect(new DistinctWordCollector()); for (String val : vals) {
System.out.println(val);
}
}
运行结果
结果确定就是我们想要的——去掉了重复的aomi
JAVA8给我带了什么——流的概念和收集器的更多相关文章
- JAVA8给我带了什么——流(入门)
JAVA8有一个新功能——流.笔者简单的看一下流.然后默默的闭上眼睛.感叹一声:这不是.NET里面的Linq吗?如果你们当中有谁做过.NET程序员的话,对于流的学习其实帮助是很大的.但是要明白你现在是 ...
- java8学习之收集器枚举特性深度解析与并行流原理
首先先来找出上一次[http://www.cnblogs.com/webor2006/p/8353314.html]在最后举的那个并行流报错的问题,如下: 在来查找出上面异常的原因之前,当然得要一点点 ...
- [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念
本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程? java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的 ...
- java8新特性学习笔记(二) 流的相关思想
流是什么 流是Java API的新成员,他允许你以声明的方式处理数据集合,就现在来说,可以把他们看成遍历数据集合的高级迭代器.此外,流还可以透明地并行处理,你无须写任何多线程代码. 下面例子是新老AP ...
- java8学习之自定义收集器深度剖析与并行流陷阱
自定义收集器深度剖析: 在上次[http://www.cnblogs.com/webor2006/p/8342427.html]中咱们自定义了一个收集器,这对如何使用收集器Collector是极有帮助 ...
- python 全栈开发,Day55(jQuery的位置信息,JS的事件流的概念(重点),事件对象,jQuery的事件绑定和解绑,事件委托(事件代理))
一.jQuery的位置信息 jQuery的位置信息跟JS的client系列.offset系列.scroll系列封装好的一些简便api. 一.宽度和高度 获取宽度 .width() 描述:为匹配的元素集 ...
- Stream01 定义、迭代、操作、惰性求值、创建流、并行流、收集器、stream运行机制
1 Stream Stream 是 Java 8 提供的一系列对可迭代元素处理的优化方案,使用 Stream 可以大大减少代码量,提高代码的可读性并且使代码更易并行. 2 迭代 2.1 需求 随机创建 ...
- 01 IO流(一)—— 流的概念、File类
1 流的概念理解(重要) 理解流的概念非常重要. 流,就是程序到数据源或目的地的一个通道. 我们把这个通道实例化得到一个具体的流,相当于一个数据传输工具,它可以在程序与资源之间进行数据交换. 换言之, ...
- java8学习之Collector源码分析与收集器核心
之前已经对流在使用上已经进行了大量应用了,也就是说对于它的应用是比较熟悉了,但是比较欠缺的是对于它底层的实现还不太了解,所以接下来准备大量通过阅读官方的javadoc反过来加深对咱们已经掌握这些知识更 ...
随机推荐
- Word frequency program ver.1
先吐槽一下自己 做这个作业的时候完全没有提前考虑好时间 导致要提交前一天晚上通宵写代码 而且还满满的bug TAT 缓一口气 程序还在继续写 先把有点累 想着先把博客写了 PART I 预计时间: 语 ...
- ajax多级菜单栏
1.jsp 首先ajax查询数据 <script type="text/javascript"> function targetlist() { $.ajax({ ur ...
- Vue命令(一)
Vue Command Summary 1.v-bind:元素节点的title属性和message保持一致. <div id="app-1"> <span v-b ...
- /var/lib/mysql 的访问权限问题 Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)
mysql 登录不进去 提示Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2) she ...
- QT 子窗口退出全屏
m_pWidget代表子窗口, 子窗口显示全屏: m_pWidget->setWindowFlags(Qt::Dialog); m_pWidget->showFullScreen(); 子 ...
- 《面向对象程序设计》第三次作业 Calculator
c++第三次作业 Calculator git上的作业展示点这里. ps:有一点不是很明确,作业要求:将数字和符号提取出来,得到一组string,然后才将这些string存入队列中.按我的理解是需要将 ...
- Maven项目中添加JDBC驱动
在pom.xml配置文件中添加: <dependency> <groupId>mysql</groupId> <artifactId>mysql-con ...
- JS 柯里化 (curry)
用 JS 理解柯里化 函数式编程风格,试图以函数作为参数传递(回调)和无副作用的返回函数(修改程序的状态). 很多语言采用了这种编程风格.JavaScript,Haskell,Clojure,Erla ...
- TitleLayout——一个Android轻松实现通用、标准、支持沉浸式状态栏的标题栏库
TitleLayout 多功能.通用的.可在布局或者使用Java代码实现标题栏:支持沉浸式状态栏,支持左侧返回按钮(不需要手动实现页面返回),左侧支持图片+文字.图片.文字:右侧支持图片.文字等. 堆 ...
- Docker(十九)-Docker监控容器资源的占用情况
启动一个容器并限制资源 启动一个centos容器,限制其内存为1G ,可用cpu数为2 [root@localhost ~]# docker run --name os1 -it -m 1g --cp ...