Java8-Stream

Scroll Down

Java8 Stream

在看到Java8的新特性之后很想了解下Java8里面的新东西,所以根据Java8出现的新Stream类以及相关操作写一些自己简单的理解,如果哪里写的不对有大神看到还希望帮忙纠正,要是有写的不好的也请帮忙指正,我也希望自己在总结的过程中可以进步,也希望能帮到其他人。

Stream对象提供多个非常有用的方法,这些方法可以分成两类:

中间操作

将原始的Stream转换成另外一个Stream;如filter返回的是过滤后的Stream。

终端操作

产生的是一个结果或者其它的复合操作;如count或者forEach操作。

map & flatmap

源码:

    /**
     * Returns a stream consisting of the results of applying the given
     * function to the elements of this stream.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element
     * @return the new stream
     */
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    
        /**
     * Returns a stream consisting of the results of replacing each element of
     * this stream with the contents of a mapped stream produced by applying
     * the provided mapping function to each element.  Each mapped stream is
     * {@link java.util.stream.BaseStream#close() closed} after its contents
     * have been placed into this stream.  (If a mapped stream is {@code null}
     * an empty stream is used, instead.)
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @apiNote
     * The {@code flatMap()} operation has the effect of applying a one-to-many
     * transformation to the elements of the stream, and then flattening the
     * resulting elements into a new stream.
     *
     * <p><b>Examples.</b>
     *
     * <p>If {@code orders} is a stream of purchase orders, and each purchase
     * order contains a collection of line items, then the following produces a
     * stream containing all the line items in all the orders:
     * <pre>{@code
     *     orders.flatMap(order -> order.getLineItems().stream())...
     * }</pre>
     *
     * <p>If {@code path} is the path to a file, then the following produces a
     * stream of the {@code words} contained in that file:
     * <pre>{@code
     *     Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8);
     *     Stream<String> words = lines.flatMap(line -> Stream.of(line.split(" +")));
     * }</pre>
     * The {@code mapper} function passed to {@code flatMap} splits a line,
     * using a simple regular expression, into an array of words, and then
     * creates a stream of words from that array.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element which produces a stream
     *               of new values
     * @return the new stream
     */
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

根据源码可以看到map和flatmap返回的都是一个stream对象,所以都是一个中间操作,但是map和flatMap有些不同,我们可以理解为map会比flatmap多了一层封装,具体理解可以参考下面示例

示例代码1:

    public static void test(){
        Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
        s.map(n -> Stream.of(n.split(""))).forEach(System.out::println);

        Stream<String> s1 = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
        s1.flatMap(n -> Stream.of(n.split(""))).forEach(System.out::println);
    }

输出结果:

java.util.stream.ReferencePipeline$Head@34c45dca
java.util.stream.ReferencePipeline$Head@52cc8049
java.util.stream.ReferencePipeline$Head@5b6f7412
java.util.stream.ReferencePipeline$Head@27973e9b
java.util.stream.ReferencePipeline$Head@312b1dae
t
e
s
t
t
1
t
2
t
e
e
e
e
e
a
a
a
a

示例代码2:

package com.troy.java.stream;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * 1.flatMap和Map都返回一个Stream
 * 2.flapMap返回的Stream是将每一个元素都当作一个Stream返回,因此flatMap的结果可以直接通过遍历拿到我们想要的单个对象的值,也就是说flatMap返回的结果是一个list
 * 同时flatmap传入的值在处理晚后返回的结果也必须是一个list;
 * 3.Map返回的Stream是将m处理的结果做为一个Stream.ReferencePipeline的集合返回,所以当我们尝试按照String来获取map出来的对象是拿不到的;
 * 4.通过对比结果我们可以这样来理解,map最后的结果是一个stream但是这个stream包裹了所有map处理的对象,当我们要拿对象的时候要多处理一层;
 * flatmap返回的直接就是处理的结果的集合,没有外面Stream包裹这一层
 */
public class Java8Map {

    public static void main(String[] args){
        List list = new ArrayList();
        list.add("hello world");list.add("Java 8");list.add("Spring Springboot SpringCloud");
        System.out.println("=============flatMap============");
        list.stream().flatMap(m->{
            System.out.println("m.toString == " + m.toString());
            return Stream.of(m.toString().split(" "));
        }).forEach(f->System.out.println(f));
        System.out.println("================================");
        System.out.println();
        System.out.println("=============Map============");
        list.stream().map(m->{
            System.out.println("m.toString == " + m.toString());
            return Stream.of(m.toString().split(" "));
        }).forEach(f->{
            System.out.println(f.getClass());
            System.out.println(f.toString());
            Iterator<String> iterable = ((Stream) f).iterator();
            while(iterable.hasNext()){
                System.out.println(iterable.next());
            }
        });
        System.out.println("================================");
    }
}

输出结果:

=============flatMap============

m.toString == hello world

hello

world

m.toString == Java 8

Java

8

m.toString == Spring Springboot SpringCloud

Spring

Springboot

SpringCloud

================================


=============Map============
m.toString == hello world
class java.util.stream.ReferencePipeline$Head
java.util.stream.ReferencePipeline$Head@b4c966a
hello
world
m.toString == Java 8
class java.util.stream.ReferencePipeline$Head
java.util.stream.ReferencePipeline$Head@2f4d3709
Java
8
m.toString == Spring Springboot SpringCloud
class java.util.stream.ReferencePipeline$Head
java.util.stream.ReferencePipeline$Head@4e50df2e
Spring
Springboot
SpringCloud
================================

通过两个示例可以看到同样的操作执行结果之后flatMap可以输出我们期待的遍历结果,但是map返回的是一个stream.ReferencePipeline,因此需要我们多做一步解析才能拿到map的value

示例代码3:

public static void test(){
        Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
        s.map(n -> Stream.of(n.split(" "))).forEach(System.out::println);
        
        Stream<String> s2 = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
        s2.map(n -> n.split(" ")).forEach(System.out::println);

        Stream<String> s1 = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
        s1.flatMap(n -> Stream.of(n.split(" "))).forEach(System.out::println);

        Stream.of(1,2,3).map(n->n+3).forEach(System.out::println);
        Stream.of(1,2,3).flatMap(n->n+3).forEach(System.out::println);
        Stream.of(1,2,3).flatMap(n->Stream.of(n+3)).forEach(System.out::println);
        String[] args = {"hello","ups","java"};
        Stream.of(args).map(m->m.concat("~m")).forEach(f->System.out.println(f));
        Stream.of(args).flatMap(m->m.concat("~m")).forEach(f->System.out.println(f));
}

我们可以看到有两行代码编译报错,这是因为flatMap里面需要的是一个mapper stream function,如果我们直接传一个function过来程序编译会报错,因此这两行编译不通过,我们也可以看到编译通过flatMap语句里面的mapper function都是一个stream包裹的操作。这一点我们也可以通过map和flatMap源码里面的注释可以看到

map注释:

Returns a stream consisting of the results of applying the given function to the elements of this stream.

flatMap注释:

Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Each mapped stream is {@link java.util.stream.BaseStream#close() closed} after its contents have been placed into this stream. (If a mapped stream is {@code null} an empty stream is used, instead.)

This is an intermediate operation.

@apiNote

The {@code flatMap()} operation has the effect of applying a one-to-many transformation to the elements of the stream, and then flattening the resulting elements into a new stream.

Stream.of(1,2,3).flatMap(n->n+3).forEach(System.out::println);
Stream.of(args).flatMap(m->m.concat("~m")).forEach(f->System.out.println(f));

注释掉这两行之后运行结果可以看到

java.util.stream.ReferencePipeline$Head@4e50df2e

java.util.stream.ReferencePipeline$Head@1d81eb93

java.util.stream.ReferencePipeline$Head@7291c18f

java.util.stream.ReferencePipeline$Head@34a245ab

java.util.stream.ReferencePipeline$Head@7cc355be

[Ljava.lang.String;@52cc8049

[Ljava.lang.String;@5b6f7412

[Ljava.lang.String;@27973e9b

[Ljava.lang.String;@312b1dae

[Ljava.lang.String;@7530d0a

test

t1

t2

teeeee

aaaa

4

5

6

4

5

6

hello~m

ups~m

java~m

最后总结:
通过几个例子可以看到map和flatMap的区别:

  1. 执行map时的匿名函数可以直接是一个方法体,不需要stream包裹,但是flatMap需要多用Stream包裹一层;
  2. map和flatMap函数结束的时候返回的结果是不一样的,map操作会比flatMap操作要多包裹一层,这点需要我们在写代码的时候注意去做合适的处理;

reduce

reduce的相关操作参考Java8-Lambda & InterfaceAPI入门里面对于BiFunction和BinaryOperator部分的介绍和示例,这里就不再过多介绍了

其他相关操作未完待续。。。