您的位置:首页 > 编程语言 > Java开发

[Java]“语法糖”系列(三)之集合流操作(Stream)[笔记]

2017-11-25 17:49 806 查看
>集合流操作

    这里的这个流和IO流不一样,是JDK8中对Collection对象功能的加强。

    个人认为作为基础,IBM上的>这篇文章<写得已经很好了,已然再无他文可出其右。这里只做一个要点笔记。

>流操作

    流操作分为两类:

Intermediate(转换操作):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
Terminal(终点操作):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
    所有的转换操作都是lazy的,lazy是指多次转换操作只会在遇到终点操作之后,才会依次执行。在终点操作之后,流截止,不再允许更多操作。比如:

List<Integer> nums = Arrays.asList(1, 2, 3);
nums.stream().peek(System.out::println).map(x->x*-1).forEach(System.out::println);
    输出为: 1 -1 2 -2 3 -3。

>流操作分类

【数据集合】 ->【数据源】 - >  若干次(包括0)【转换操作】 ->【终点操作】。
     >这篇文章<把常用操作都用图表示出来了,值得记录一下。

     转换操作:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel...

     终点操作:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny...

     其实还有一种短路操作,下面再说。

>终点操作:Collect方法

    看下面这段代码:

Set<String> result = Stream.of("aa", "bb", "cc", "aa").collect(
() -> new HashSet<String>(),
(set, item) -> set.add(item),
(set, subSet) -> set.addAll(subSet));
   其中collect方法源码为:

<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
    Supplier是一个函数式接口:

@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
    其功能为直接返回一个传入的实例。() -> new HashSet<String>()等价于:

new Supplier<Set<String>>() {
@Override
public Set<String> get() {
return new HashSet<>();
}
}
    BiConsumer也是一个函数式接口,接受两个传入的参数,无返回值。(set, item) -> set.add(item)等价于:

new BiConsumer<Set<String>, String>() {
@Override
public void accept(Set<String> set, String item) {
set.add(item);
}
}
    同理(set, subSet) -> set.addAll(subSet))等价于:
new BiConsumer<Set<String>, Set<String>>() {
@Override
public void accept(Set<String> set, Set<String> subSet) {
set.addAll(subSet);
}
}
   collect方法在ReferencePipeLine.java -> ReduceOps.java中被实现:
public static <T, R> TerminalOp<T, R>
makeRef(Supplier<R> seedFactory,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R,R> reducer) {
Objects.requireNonNull(seedFactory);
Objects.requireNonNull(accumulator);
Objects.requireNonNull(reducer);
class ReducingSink extends Box<R>
implements AccumulatingSink<T, R, ReducingSink> {
@Override
public void begin(long size) {
state = seedFactory.get();
}

@Override
public void accept(T t) {
accumulator.accept(state, t);
}

@Override
public void combine(ReducingSink other) {
reducer.accept(state, other.state);
}
}
return new ReduceOp<T, R, ReducingSink>(StreamShape.REFERENCE) {
@Override
public ReducingSink makeSink() {
return new ReducingSink();
}
};
}

     最终由StreamPipe送入每一个数据,通过累加器 accumulator 叠加到集合中。

     <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);接受的三个函数参数都有具体的要求,第一个参数要求返回一个产生数据容器result的实例;第二个函数参数要求是一个无状态、无打断(异常)的累加函数,用于把数据流中的每个数据装入result容器中;第三个函数参数要求传入一个与累加函数兼容的结合函数,用于叠加并行流的多个数据容器。

    在经过方法引用简写后,上文的例子可简化为:

Set<String> result = Stream.of("aa", "bb", "cc", "aa").collect(
HashSet::new,
HashSet::add,
HashSet::addAll);
    事实上对于Java提供的集合对象,已有封装好的方法:

Set<String> result = Stream.of("aa", "bb", "cc", "aa").collect(Collectors.toSet());


>函数接口

     上面那个例子里提到了函数接口,我找到了一张描述常用函数接口的图:



>短路操作Short-circuiting()

     其实不难发现,所有的转换操作最终返回的都是一个流,而终点操作不再返回一个流,而是直接触发了Pipe中取出数据的操作,给所有激活所有沿途lazy的转换操作。

     短路操作其实和终点操作也是一样的,可能不再返回一个流,或是返回一个被截取过的流。比如anyMatch方法,通过Predicate<T>接口返回了一个真值。

     由于流Stream在理论上是无限大的,短路操作被用以对流进行截取,把无限的变成有限的流,比如limit方法,可以限制获取数据源的数量。

     短路操作:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit...

     下面这个例子是就是一个短路操作,用于检测数据流中是否含有"aa"字符串。相比于迭代的方式,这钟写法更偏向自然语言。

boolean string = Stream.of("aa", "bb", "cc", "dd").anyMatch("aa"::equals);
System.out.println(string);
     再举一个关于短路操作的例子。findFirst方法用于获取数据流中的第一个数据,返回一个Optional对象。Optional对象是Java仿Scala语言设计的一种可空对象,在>这篇文章<里有详尽解释。
Stream.of("aa", "bb", "cc", "dd").findFirst().ifPresent(System.out::println);// 打印 aa


>海纳百川的流:flatMap

    Stream是无限的,并且流也接受不同的数据类型。这就带来一个问题,Steam是Collection类的功能加强,但Collcetion显然不能一次性接受所有的数据类型。

    Steam中提供了一个接口方法flatMap,用于把不同的数据集拍平,无论你是List还是Set,统一还原成数据源Source。

    例如我现在有个学校,学校下有若干个班级,班级里有若干个学生。我要把这个学校直接拍平并转化成学生的数据流:

List<List> school = new LinkedList<>();
List<String> class1 = new LinkedList<>();
List<String> class2 = new LinkedList<>();
class1.add("小明");class1.add("小王");
class2.add("小李");
school.add(class1);school.add(class2);

school.stream().flatMap(e->e.stream()).forEach(System.out::println);// 打印: 小明 小王 小李

如果需要拍平二层以上的流,如现在有一个List<List>region.add(school),对于拍平、并获取整个地区所有学校所有班级的学生名字的流,需要做一个转型,这么写:

region.stream()
.flatMap(Collection::stream)// 用了"方法引用"的语法糖,详见:http://blog.csdn.net/shenpibaipao/article/details/78614280
.flatMap(e->((List<String>) e).stream())// 转型成Collection形式
.forEach(System.out::println);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  jdk8 steam lambda