stream_streamsets - CSDN
stream 订阅
Stream泛指流媒体技术。流媒体实际指的是一种新的媒体传送方式,而非一种新的媒体,是指采用流式传输的方式在Internet播放的媒体格式。可指大河,也可指小河或小溪,指小溪时与brook,creek同义。creek侧重其狭长蜿蜒,缓缓流动,且多流入大河或湖泊。brook侧重发源于山泉。creek和stream都比brook大。stream还可引申表示事物连绵不断。stream还可作动词,意为“流动,飘动”。常用作不及物动词,也可用作及物动词。 展开全文
Stream泛指流媒体技术。流媒体实际指的是一种新的媒体传送方式,而非一种新的媒体,是指采用流式传输的方式在Internet播放的媒体格式。可指大河,也可指小河或小溪,指小溪时与brook,creek同义。creek侧重其狭长蜿蜒,缓缓流动,且多流入大河或湖泊。brook侧重发源于山泉。creek和stream都比brook大。stream还可引申表示事物连绵不断。stream还可作动词,意为“流动,飘动”。常用作不及物动词,也可用作及物动词。
信息
词    性
动词,名词
中文名
流媒体技术
外文名
Stream
基本解释
泛指流媒体技术
stream英语单词
基本释义v. [striːm] ( streams; streamed; streaming )vt. & vi. 流; 移动 flow freely; move continuously and smoothly in one directionvi. 飘扬; 招展 float or wave (in the wind)词语要点1.stream的基本意思是“流动”,指受限制的流动,如通过一定的路线或出口。也可指大量不断地流动。引申可指“飘动”。2.stream既可用作及物动词,也可用作不及物动词。用作及物动词时,可接名词或代词作宾语。3.stream接介词with表示“被…覆盖”。词语搭配~+副词stream torrentially 激流涌进stream back 向后飘动stream in 络绎进入~+介词stream behind 在…后面飘动stream down one's cheeks (眼泪)从脸颊流下stream into the auditorium (人群)络绎不绝进入礼堂stream out of the station (人群)涌出车站stream with 被…覆盖辨析pour, flow, run, stream这组词的共同意思是“流”“流动”。其区别是:1.flow, run, stream和pour都可指液体流动; flow还可指气体流动; pour还可指光线、微粒等倾泻。2.时间上:flow一般指源源不断地、长时间地流动; run既可以是源源不断地、长时间地流动,也可以是流动一段时间; stream和pour多指短时间地流动。3.方向上:指液体流动时, flow是水平流动; run和stream既可水平流动,也可垂直流动; pour是垂直流动。4.流速上:从快到慢依次为pour, stream, run, flow。具体说就是flow是平平稳稳地流动; run比较湍急; stream比run更有力; pour则是“倾泻”。5.flow, stream和pour常用于比喻, run很少用于比喻。例如:The river was flowing quietly.河水静静流着。She let her hair down so that it flew darkly over her shoulders.她让乌黑的头发披散下来,飘垂到肩上。  The river runs through hills and fields.河水流经山冈和田野。The water runs out of the pipe into the bucket.水自管内注入水桶中。Tears were streaming down her face.她脸上热泪滚滚而下。The students streamed into the auditorium.学生们络绎不绝地进入礼堂。下面三句话的意思相同:She poured me a cup of tea.She poured a cup of tea for me.She poured me out a cup of tea.她给我倒了一杯茶。词源<古英语stream(流动)基本释义C 小河,溪流 a small river C 流,一股,一串 flow (of liquid, people, things, etc.)S 水流方向,潮流 current or direction of sth flowing or movingC (按能力分的)班级 class or division of a class into which children of the same age and level of ability are placedC 川流不息 a continuous series of people, things, or events, usually moving in a line or in a certain direction常见搭配动词+~cross a stream 涉过一条小溪tap a stream 引流形容词+~clear〔dancing, quiet, running〕 stream 清澈〔奔腾欢跳,平静,流动〕的溪流rapid〔strong〕 stream 湍急〔强劲〕的水流rushing stream 激流名词+~mountain stream 山涧sun streams 太阳光线介词+~in streams 连续不断,川流不息on stream 进行生产,投入生产up the stream 向〔在〕上游~+介词a stream of light 一缕光线a stream of words 滔滔不绝的话the stream of history 历史潮流词语辨析branch,brook,canal,creek,river,stream,torrent这组词的共同意思是“流水的通道”。其区别是:1.除canal指人工开挖的河流或渠道外,其余各词均指自然形成的水道。2.river和torrent均指流量较大的河流,river可泛指(自然形成的)江河;torrent则特指急流、湍流。这两个词还常用于比喻。3.stream可指大河,也可指小河或小溪,指小溪时与brook,creek同义。creek侧重其狭长蜿蜒,缓缓流动,且多流入大河或湖泊。brook侧重发源于山泉。creek和stream都比brook大。stream还可引申表示事物连绵不断。4.branch指江河的支流。同义词n. brook, course, flow, rush, streamlet [1] 
收起全文
  • Stream初理解  JAVA8中一个重要概念就是流——stream。先来看看流的使用: public class StreamTest01 { public static void main(String[] args) { Stream stream = Stream.of(1,2,3,4); stream.map(i ->...
        Stream初理解

        JAVA8中一个重要概念就是流——stream。先来看看流的使用:

    public class StreamTest01 {
    
        public static void main(String[] args) {
           Stream<Integer> stream = Stream.of(1,2,3,4);
    
           stream.map(i -> i+1).forEach(System.out::println);
        }
    }

         上面的代码是创建了一个包含(1,2,3,4)四个元素的流,并将流中的元素分别加一,然后打印输出。也许这样不太直观。可以再看看下面的写法:

    public class StreamTest01 {
    
        public static void main(String[] args) {
           List<Integer> list = Arrays.asList(1,2,3,4);
           Stream<Integer> stream = list.stream();
           stream.map(i -> i+1).forEach(System.out::println);
        }
    }
    
    上面的这段代码也是将四个元素加一然后输出到控制台。不同的是这里是通过一个集合创建流对象,然后进行操作的。同样的需求,流对象带来的便利性是显而易见,直观、易读且易编写。开发人员不用关心元素是如何取出的,也不用关心操作之间应该如何连接(以前的写法是,我们必须保证加一操作和输出操作之间的执行顺序);开发人员只需要关心每个操作的具体执行逻辑。

           文档将对其的解释是:流是一些聚合操作,可以并发和顺序执行。从这个定义中可以看出流的两个特征:

           1、流是一系列操作的集合。

           2、流可以并发执行,也可以顺序执行。

    既然流是一些操作的集合,那么流就可以将一个或多个操作聚集起来。这里就有两个问题:1、流是如何将这些操作聚集起来的;2、流是如何创建的的,只有知道流的创建过程,才知道流是如何连接操作的。

          上面用到了Stream.of()方法返回了一个流对象。意味着这个方法包含了流的创建过程,先看看他的源代码:

    //Stream接口
    public static<T> Stream<T> of(T... values) {
            return Arrays.stream(values);
    }
    
    //Arrays类
    public static <T> Stream<T> stream(T[] array) {
            return stream(array, 0, array.length);
    }
    
    //Arrays类
    public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
            return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
    }
    
    //StreamSupport类
    public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
            Objects.requireNonNull(spliterator);
            return new ReferencePipeline.Head<>(spliterator,
                                                StreamOpFlag.fromCharacteristics(spliterator),
                                                parallel);
    }
    上面是Stream.of()方法的源代码调用过程,可以看到最后调用了StreamSupport类的stream()方法,在stream方法中创建了一个ReferencePipeline.Head类(这是ReferencePipeline的一个内部类)的对象。接下来再看看Head这个内部类的源代码: 

    //Head类
    static class Head<E_IN, E_OUT> extends ReferencePipeline<E_IN, E_OUT> {      
            Head(Supplier<? extends Spliterator<?>> source,
                 int sourceFlags, boolean parallel) {
                super(source, sourceFlags, parallel);
            } 
    ……
    }
    
    // ReferencePipeline类
    abstract class ReferencePipeline<P_IN, P_OUT> extends AbstractPipeline<P_IN, P_OUT, Stream<P_OUT>>
            implements Stream<P_OUT>  {
        ReferencePipeline(Supplier<? extends Spliterator<?>> source,int sourceFlags, boolean parallel) {
            super(source, sourceFlags, parallel);
    }
    ……
    }
    
    // AbstractPipeline类
    /**
    * Constructor for the head of a stream pipeline.
    *
    * @param source {@code Spliterator} describing the stream source
    * @param sourceFlags the source flags for the stream source, described in
    * {@link StreamOpFlag}
    * @param parallel {@code true} if the pipeline is parallel
    */
    abstract class AbstractPipeline<E_IN, E_OUT, S extends BaseStream<E_OUT, S>>
            extends PipelineHelper<E_OUT> implements BaseStream<E_OUT, S> {
            AbstractPipeline(Supplier<? extends Spliterator<?>> source,
                         int sourceFlags, boolean parallel) {
                  this.previousStage = null;
                  this.sourceSupplier = source;
                  this.sourceStage = this;
                  this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;
                  // The following is an optimization of:
                  // StreamOpFlag.combineOpFlags(sourceOrOpFlags, StreamOpFlag.INITIAL_OPS_VALUE);
                  this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
                  this.depth = 0;
                  this.parallel = parallel;
            }
            ……
    }
    这里只截取了创建Head实例的部分代码,可以看到在Head的构造方法中什么也没做,直接调用了父类的构造方法。最后Head的构造方法调用的是AbstractPipeline类的构造方法。在AbstractPipeline的构造方法执行完成后,流对象就创建完成了。

          但是另外一个问题又出现了。这么云山雾绕的转了一大圈的创建了一个对象,不知道是做什么用的。为什么要这么做,与流的创建又有什么关系?其实,在AbstractPipeline的构造方法我将注释也截取下来了,注释有这么一句话:

    Constructor for the head of a stream pipeline(构造一个流管道的头部).这句话透露了两个信息:1、我们最后构造的是一个流管道,也就是说流对象是以管道的形式表现的;2、这个构造方法仅仅是构造管道的头部,也就是管道的开端,那么流管道的其他部分呢?

          因此,一定有构造流管道其他部分的方法的。这个方法就在AbstractPipeline类的另外一个构造方法里面,看一下AbstractPipeline类的另外一个构造方法:

     /**
     * Constructor for appending an intermediate operation stage onto an
     * existing pipeline.
     *
     * @param previousStage the upstream pipeline stage
     * @param opFlags the operation flags for the new stage, described in
     * {@link StreamOpFlag}
     */
    AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {
    	if (previousStage.linkedOrConsumed)
    		throw new IllegalStateException(MSG_STREAM_LINKED);
    	previousStage.linkedOrConsumed = true;
    	previousStage.nextStage = this;
    
    	this.previousStage = previousStage;
    	this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;
    	this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);
    	this.sourceStage = previousStage.sourceStage;
    	if (opIsStateful())
    		sourceStage.sourceAnyStateful = true;
    	this.depth = previousStage.depth + 1;
    }
    留意注释部分(Constructor for appending an intermediate operation stage onto an existing pipeline.)。根据注释的解释,这个构造方法构造的对象有两个特征:

    1、是一个中间操作阶段对象(an  intermediate operation stage);

    2、这个中间操作阶段对象会被追加到一个已经存在的管道上。

    AbstractPipeline中有几个属性需要留意一下:

    /**
     * Backlink to the head of the pipeline chain (self if this is the source
     * stage).
     */
    @SuppressWarnings("rawtypes")
    private final AbstractPipeline sourceStage;
    
    /**
     * The "upstream" pipeline, or null if this is the source stage.
     */
    @SuppressWarnings("rawtypes")
    private final AbstractPipeline previousStage;
    
    /**
     * The next stage in the pipeline, or null if this is the last stage.
     * Effectively final at the point of linking to the next pipeline.
     */
    @SuppressWarnings("rawtypes")
    private AbstractPipeline nextStage;
    这三个属性都是AbstractPipeline对象,分别表述pipeline的开端(sourceStage)、上一个操作(previousStage)、下一个操作(nextStage)。再结合AbstractPipeline的两个构造方法看一下:

    //构造方法一:构造管道的开端
    this.previousStage = null;   //因为是开端,没有前一个状态
    this.sourceStage = this;     //因为是开端,所以pipeline的源阶段就是当前对象
    
    //构造方法二:构造流的中间操作
    previousStage.nextStage = this;      //前一个操作的nextStage就是当前对象
    this.previousStage = previousStage;  //当前操作的前一个操作就是previousStage
    this.sourceStage = previousStage.sourceStage;  //无论管道有多长,其源都只会有一个(就像水管无论有多长,其开端只有一个),所以等于 previousStage.sourceStage
    综上所述,我们可以得到流的结构是一种双向链表的管道结构,如下图:


    流将每个操作封装成一个AbstractPipeline对象,然后通过previousStage、nextStage属性将当前操作的前一个操作和后一个操作连接起来,以此来保证流的将操作联合的特性。

            Stream的源
            Stream是一些列操作的集合,其需要一个操作的数据源,这个数据源就是Stream的源。在AbstractPipeline提供了两个构造流开端的方法,如下:
    AbstractPipeline(Supplier<? extends Spliterator<?>> source,int sourceFlags, boolean parallel) {
    	this.previousStage = null;
    	this.sourceSupplier = source;
    	this.sourceStage = this;
    	……
    }
    
     AbstractPipeline(Spliterator<?> source,int sourceFlags, boolean parallel) {
    	this.previousStage = null;
    	this.sourceSpliterator = source;
    	this.sourceStage = this;
    	……
    }
    上面两个构造方法中有一个变量(source),这个就是外部传入的数据源。在两个构造方法中,这个源分别被赋给了sourceSupplier 、sourceSpliterator两个属性。其实两个属性是一个意思,sourceSupplier是supplier(不接受参数,返回一个值)的一个实例,其返回的也是一个Spliterator对象。只是提供两种不同的源提供方式,但是其提供的源都是一样的。所以官方文档对这两个属性的介绍是:两个属性被使用了其中一个,另外一个必须为null(也就是说这个两个属性是互斥的)。在流使用完成后,这两个属性都必须被设置为null(流使用完了,就不再需要源了)。
      从上面的描述中可以看到source对象是一个Spliterator的实例。来看看Spliterator的源代码:
    public interface Spliterator<T> {
        
        boolean tryAdvance(Consumer<? super T> action);  //消耗式获取元素,元素一旦被取出,分割器将不会拥有这个元素
    
        default void forEachRemaining(Consumer<? super T> action) {   //遍历元素
            do { } while (tryAdvance(action));
        }
     
        Spliterator<T> trySplit();  //分割数据源
       
        default long getExactSizeIfKnown() {
            return (characteristics() & SIZED) == 0 ? -1L : estimateSize();
        }
     
        default boolean hasCharacteristics(int characteristics) {
            return (characteristics() & characteristics) == characteristics;
        }
    
        default Comparator<? super T> getComparator() {
            throw new IllegalStateException();
        } 
    }
    官方文档将Spliterator解释为:是一个能够对源数据进行分区和遍历操作的对象。从其实现类中,可以更好的理解这个接口。Spliterator的实现类分为三类:
          1、ArraySpliterator       (IntArraySpliterator,LongArraySpliterator,DoubleArraySpliterator);
          2、AbstractSpliterator  IntAbstractSpliterator,LongAbstractSpliterator,DoubleAbstractSpliterator
          3、IteratorSpliterator    IntIteratorSpliterator,LongIteratorSpliterator,DoubleIteratorSpliterator
          关于这个三个实现类:
          ArraySpliterator主要面向数组类型的数据源;
          IteratorSpliterator面向的是集合类数据源;
          AbstractSpliterator是给使用者自定义的,使用者可以根据自己需要扩展相应的Spliterator,这个类没有给出数据源类型,需要使用者自定义。
          这个三个类的实现方式很类似,都有一些共同的特点:1、持有一个数据源;2、设置了数据源的大小、切分单位、切分的初始值;3、定义了分割器的特征(这里不做讨论)。从源代码中看看这些特征:
    public static abstract class AbstractSpliterator<T> implements Spliterator<T> {
            static final int BATCH_UNIT = 1 << 10;  // 切分数据源的单位,每次切分的增量。即batch+BATCH_UNIT就是本次切分的长度;这里默认是1024
            static final int MAX_BATCH = 1 << 25;   //允许切分的最大长度,即batch+BATCH_UNIT是当前切分的长度,这个长度不能大于MAX_BATCH。如果大于,则等于MAX_BATCH;
            private final int characteristics;      //分割器的特征
            private long est;            // 数据源的长度(集合的长度)
            private int batch;           // 切分的初始值,创建时一般是0
    		……
    }
    
    static class IteratorSpliterator<T> implements Spliterator<T> {
            static final int BATCH_UNIT = 1 << 10;  // 切分数据源的单位,每次切分的增量。即batch+BATCH_UNIT就是本次切分的长度;这里默认是1024
            static final int MAX_BATCH = 1 << 25;  //允许切分的最大长度,即batch+BATCH_UNIT是当前切分的长度,这个长度不能大于MAX_BATCH。如果大于,则等于MAX_BATCH;
            private final Collection<? extends T> collection; //原始数据
            private Iterator<? extends T> it;
            private final int characteristics;  // 分割器的特征
            private long est;             // 数据源的长度(集合的长度)
            private int batch;            // 切分的初始值,创建时一般是0
    		……
    }
    
    static final class ArraySpliterator<T> implements Spliterator<T> {
            
            private final Object[] array;  //原始数据
            private int index;        // 切分的初始值,创建时一般是0
            private final int fence;  // 栅栏,即数据源的长度(数组的长度)
            private final int characteristics;// 分割器的特征
    		……
    	//注:这里没有给出切分的增量单位(BATCH_UNIT)和允许切分的最大长度(MAX_BATCH),是因为这种类型的分割器在其             trySplite()方法中,通过index和fence计算出来,自然也就不用给值了。
    }
    再通过一个示例感受一下Spliterator:
    public class StreamTest01 {
    
        public static void main(String[] args) {
            String[] strs = new String[]{"a","b","c","d","e","f","g","h","i","j","k"};  //原始数据
            Spliterator<String> spliterator1 = Spliterators.spliterator(strs, 0); //创建分割器
            Spliterator<String> spliterator2 = spliterator1.trySplit(); //调用分割方法,切分数据源
    
            spliterator1.forEachRemaining(i -> System.out.print(i+","));  //打印出"分割器1"拥有的所有元素
            System.out.println();//用于换行
            System.out.println("------------------");
            spliterator2.forEachRemaining(i -> System.out.print(i+","));//打印出"分割器2"拥有的所有元素
        }
    }
    //输出:
    //f,g,h,i,j,k,
    //------------------
    //a,b,c,d,e
    
    
    public class StreamTest01 {
    
        public static void main(String[] args) {
            String[] strs = new String[]{"a","b","c","d","e","f","g","h","i","j","k"};  //原始数据
            Spliterator<String> spliterator1 = Spliterators.spliterator(strs, 0); //创建分割器
            spliterator1.tryAdvance(i -> System.out.print(i+",")); //取出a
            spliterator1.tryAdvance(i -> System.out.print(i+",")); //取出b
            spliterator1.tryAdvance(i -> System.out.print(i+",")); //取出c
            spliterator1.tryAdvance(i -> System.out.print(i+",")); //取出d
        }
    }
    
    //输出:a,b,c,d,
    接下来看看IteratorSpliterator的源代码实现(其他两个实现的思想是一样的,只是代码的写法上有些微的差异):
    static class IteratorSpliterator<T> implements Spliterator<T> {
    
            /**
             *
             * 这个方法的主要作用是,在每次调用时,根据事先设置好的切分单位和相关属性
             * 将原始数据源进行切分为多个数据源,并将切分出来的数据源包装为新的Spliterator
             */
             @Override
             public Spliterator<T> trySplit() {
    
                 Iterator<? extends T> i;
                 long s;
                 if ((i = it) == null) {
                     i = it = collection.iterator();
                     s = est = (long) collection.size();
                 }
                 else
                    s = est;
                 if (s > 1 && i.hasNext()) {   //判断是否还有元素可以进行拆分
                    int n = batch + BATCH_UNIT;  //获取切分长度
                 if (n > s)  //如果切分长度大于元素的个数,则切分长度等于元素个数
                     n = (int) s;
                 if (n > MAX_BATCH)  //如果切分长度大于最大允许切分长度,则切分长度等于最大可切分长度
                     n = MAX_BATCH;
                 Object[] a = new Object[n]; //从这一步开始主要做两件事:1、将元素取出,2、将元素包装为Spliterator
                 int j = 0;
                     do { a[j] = i.next(); } while (++j < n && i.hasNext());
                     batch = j;
                     if (est != Long.MAX_VALUE)
                         est -= j;
                     return new ArraySpliterator<>(a, 0, j, characteristics);
                 }
                 return null;
             }
    
            /**
             * 这个方法是遍历出所有的元素,因为是集合类型数据,所以使用了集合的迭代器
             * 在Spliterator接口中,这个方法会调用tryAdvance()方法,依次取出元素。如:
             *  default void forEachRemaining(Consumer<? super T> action) {do { } while (tryAdvance(action));}
             *  但仅仅只是一种取出元素的标准,如果实现类可以有自己的取出元素的方式,则也可以直接覆盖
             * @param action
             */
            @Override
             public void forEachRemaining(Consumer<? super T> action) {
                 if (action == null) throw new NullPointerException();
                 Iterator<? extends T> i;
                 if ((i = it) == null) {
                     i = it = collection.iterator();
                     est = (long)collection.size();
                 }
                 i.forEachRemaining(action);
             }
    
            /**
             * 这个一个取出元素的方法,每次取出一个元素后,Spliterator将不再拥有这个元素
             * (可能删除了元素,也可能并没有删除,只是Spliterator再也取不到这个元素了)
             * @param action
             * @return
             */
             @Override
             public boolean tryAdvance(Consumer<? super T> action) {
                 if (action == null) throw new NullPointerException();
                     if (it == null) {
                     it = collection.iterator();
                     est = (long) collection.size();
                 }
                 if (it.hasNext()) {
                     action.accept(it.next());
                     return true;
                 }
                 return false;
             }
    
            /**
             * 获取源数据的长度。这个是一个估计值,因为当计算获取数据源的成本很高时,同时又进行拆分;
             * 此时获取数据源的长度,就有可能导致计算出的数据源长度不精确。或者数据源的长度没有限制,或者是
             * 未知的数据源,则无法获取精确地值。
             * 在没有进行分组遍历,且Spliterator具有SIZE特性;此时这个方法获取的值一定等于Spliterator遍历
             * 所有元素的次数
             * @return
             */
             @Override
             public long estimateSize() {
                 if (it == null) {
                     it = collection.iterator();
                     return est = (long)collection.size();
                 }
                 return est;
             }
    
             @Override
             public int characteristics() { return characteristics; }
    
            /**
             * 获取元素的比较器,由子类自定义,也可以直接抛异常
             * @return
             */
             @Override
             public Comparator<? super T> getComparator() {
                 if (hasCharacteristics(Spliterator.SORTED))
                     return null;
                     throw new IllegalStateException();
            }
    }
    通过上面的描述,Stream的数据源是通过一个Spliterator的实例持有一个原始数据源来提供的。

          Stream的中间操作和终止操作

          在上面的内容中,说到流有三个部分:开端、终端、中端。根据上面的描述,流的开端保存了数据的源,提供了一定的数据源的操作方法。而对于中端和终端,则为进行任何的描述。在JDK的文档中,将流定义为一系列操作的集合,同时JDK文档又将这些操作分为两类,即中间操作和终止操作。看一段代码,直观感受一下中间操作和终止操作:

    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;
    
    public class StreamTest01 {
    	
    	public static void main(String[] args) {
    		  List<Integer> lis = Arrays.asList(1,2,3,4,5,6,7,8,9);
    		  Stream<Integer> stream1 = lis.stream().map(i -> i+1);  //中间操作
    		  Stream<Integer> stream2 = stream1.filter(i -> i > 0);  //中间操作
    		  Stream<Integer> stream3 = stream2.limit(10);  //中间操作
    		  Stream<Integer> stream4 = stream3.distinct(); //中间操作
    		  
    		  //Optional<Integer> result1 = stream4.findAny(); //终止操作
    		  //Optional<Integer> result2 = stream4.findFirst(); //终止操作,两次调用终止操作,会抛异常,流只能被使用一次,可以中间操作,也可以是终止操作
    		  stream4.forEach(i -> System.out.print(i));  //终止操作,没有返回值
    		  
    		  //上面的所有操作有另外的一种写法
    		  lis.stream().map(i -> i+1).filter(i -> i > 0)
    		  	 .limit(10).distinct().forEach(i -> System.out.print(i));
    	} 
    }
    从上面的代码可以看到,中间操作返回的都是Stream对象;终止操作可能有返回值,可能没有;而且返回的值类型不定。

          接下来从源代码角度看一下中间操作和终止操作都是如何运行的。这里选取map方法和foreach方法作为分析对象,其他方法的实现方式类似。 

    //类名:ReferencePipeline;方法:map
    public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
    	Objects.requireNonNull(mapper);  留意StatelessOp
    	return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,  
    								 StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
    		@Override
    		Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
    			return new Sink.ChainedReference<P_OUT, R>(sink) {
    				@Override
    				public void accept(P_OUT u) {
    					downstream.accept(mapper.apply(u));
    				}
    			};
    		}
    	};
    }
    
    //类名:ReferencePipeline;方法:forEach
    public void forEach(Consumer<? super P_OUT> action) {
    	evaluate(ForEachOps.makeRef(action, false));  //调用了AbstractPipeline的evaluate方法
    }
    
    //类名:AbstractPipeline;方法:evaluate
    final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {  //留意TerminalOp
    	assert getOutputShape() == terminalOp.inputShape();
    	if (linkedOrConsumed)
    		throw new IllegalStateException(MSG_STREAM_LINKED);
    	linkedOrConsumed = true;
    
    	return isParallel() ? terminalOp.evaluateParallel(this,sourceSpliterator(terminalOp.getOpFlags()))  : terminalOp.evaluateSequential(this,sourceSpliterator(terminalOp.getOpFlags()));
    }

    在map方法中创建了StatelessOp对象。在foreach方法中通过ForEachOps.makeRef(action, false)方法创建了一个TerminalOp对象。

         StatelessOp比较简单,StatelessOp继承了AbstractPipeline,并覆写了opWrapSink方法。在opWrapSink方法中创建了一个Sink.ChainedReference对象。StatelessOp继承了AbstractPipeline,这里调用StateleddOp的构造方法,实际上是调用了AbstractPipeline的创建中间操作对象的构造方法(前面提到过AbstractPipeline的构造方法有两个,一个构造流的开端,一个构造中间操作),构造了一个AbstractPipeline对象,这个对象表示流的中间操作。

            TerminalOp对象的创建则相对复杂一些。在这之前需要了解两个接口:TerminalOp和TerminalSink,源代码如下:

    //接口:TerminalOp
    interface TerminalOp<E_IN, R> {
        
        default StreamShape inputShape() { return StreamShape.REFERENCE; }
    
         
        default int getOpFlags() { return 0; }
    
         
        default <P_IN> R evaluateParallel(PipelineHelper<E_IN> helper,
                                          Spliterator<P_IN> spliterator) {
            if (Tripwire.ENABLED)
                Tripwire.trip(getClass(), "{0} triggering TerminalOp.evaluateParallel serial default");
            return evaluateSequential(helper, spliterator);
        }
    
        
        <P_IN> R evaluateSequential(PipelineHelper<E_IN> helper,
                                    Spliterator<P_IN> spliterator);
    }
    
    //接口:TerminalSink
    interface TerminalSink<T, R> extends Sink<T>, Supplier<R> { }

    在TerminalOp接口中分别定义了顺序执行的方法(evaluateSequential)和并行执行的方法(evaluateParallel);留心一下会发现,并行计算的方法调用了顺序执行的方法。也就是说,默认情况下是没有并行的(因为并行调用了顺序执行的方法);具体的并行的执行方式需要具体的子类去实现。而对于TerminalSink接口,则没有任何的实现,直接继承了Sink接口。

    接下来再看看ForEachOps.makeRef(action, false)的执行内容:

    //类名:ForEachOps ; 方法名:makeRef
    public static <T> TerminalOp<T, Void> makeRef(Consumer<? super T> action,boolean ordered) {
    	Objects.requireNonNull(action);
    	return new ForEachOp.OfRef<>(action, ordered);
    }
    
    //类名:ForEachOps.OfRef 
    static final class OfRef<T> extends ForEachOp<T> {
    	final Consumer<? super T> consumer;
    
    	OfRef(Consumer<? super T> consumer, boolean ordered) {
    		super(ordered);
    		this.consumer = consumer;
    	}
    
    	@Override
    	public void accept(T t) {
    		consumer.accept(t);
    	}
    }
    
    //类名:ForEachOp
    static abstract class ForEachOp<T> implements TerminalOp<T, Void>, TerminalSink<T, Void> {
    	private final boolean ordered;
    
    	protected ForEachOp(boolean ordered) {
    		this.ordered = ordered;
    	}
    	……
    }

    从上面的代码可以看到ForEachOps.makeRef(action, false)创建了一个 ForEachOps.OfRef 对象。ForEachOps实现了TerminalOp和TerminalSink接口,同时也实现了Sink接口。

    无论是StatelessOp还是TerminalOp,最终都关联了Sink接口。其实流的所有操作都是由Sink接口的各种实现类来串联的。看一下Sink接口的代码: 

    interface Sink<T> extends Consumer<T> {
        
        default void begin(long size) {}
      
        default void end() {}
      
        default boolean cancellationRequested() {
            return false;
        }
       
        default void accept(int value) {
            throw new IllegalStateException("called wrong accept method");
        }
       
        default void accept(long value) {
            throw new IllegalStateException("called wrong accept method");
        }
      
        default void accept(double value) {
            throw new IllegalStateException("called wrong accept method");
        }
    	
    	static abstract class ChainedReference<T, E_OUT> implements Sink<T> {
            protected final Sink<? super E_OUT> downstream;
    
            public ChainedReference(Sink<? super E_OUT> downstream) {
                this.downstream = Objects.requireNonNull(downstream);
            }
    
            @Override
            public void begin(long size) {
                downstream.begin(size);
            }
    
            @Override
            public void end() {
                downstream.end();
            }
    
            @Override
            public boolean cancellationRequested() {
                return downstream.cancellationRequested();
            }
        }
     
        static abstract class ChainedInt<E_OUT> implements Sink.OfInt {
            protected final Sink<? super E_OUT> downstream;
    
            public ChainedInt(Sink<? super E_OUT> downstream) {
                this.downstream = Objects.requireNonNull(downstream);
            }
    
            @Override
            public void begin(long size) {
                downstream.begin(size);
            }
    
            @Override
            public void end() {
                downstream.end();
            }
    
            @Override
            public boolean cancellationRequested() {
                return downstream.cancellationRequested();
            }
        }
     
        static abstract class ChainedLong<E_OUT> implements Sink.OfLong {
            protected final Sink<? super E_OUT> downstream;
    
            public ChainedLong(Sink<? super E_OUT> downstream) {
                this.downstream = Objects.requireNonNull(downstream);
            }
    
            @Override
            public void begin(long size) {
                downstream.begin(size);
            }
    
            @Override
            public void end() {
                downstream.end();
            }
    
            @Override
            public boolean cancellationRequested() {
                return downstream.cancellationRequested();
            }
        }
     
        static abstract class ChainedDouble<E_OUT> implements Sink.OfDouble {
            protected final Sink<? super E_OUT> downstream;
    
            public ChainedDouble(Sink<? super E_OUT> downstream) {
                this.downstream = Objects.requireNonNull(downstream);
            }
    
            @Override
            public void begin(long size) {
                downstream.begin(size);
            }
    
            @Override
            public void end() {
                downstream.end();
            }
    
            @Override
            public boolean cancellationRequested() {
                return downstream.cancellationRequested();
            }
        }	
    }

            这里采用了模板方法模式,规定了Sink方法的调用必须是begin,accept,end的顺序,可以重复调用,但必须保证调用方法的顺序(begin,accept,end)。

            在这个接口中有三个重载的accept方法,却没有accept(Object  value),这是因为Sink接口继承了Consumer接口,在Consumer接口中已经有一个接收Object参数的accept方法了。

         此外,Sink接口中定义了四个实现类(ChainedReference,ChainedInt,ChainedLong,ChainedDouble),这四个实现类中都分别持有一个downstream的Sink对象,这个对象是实现Stream中操作链接的关键。

           前面都是准备阶段,接下来看看Stream是如何将所有的操作链接起来进行的。在调用map方法的时候,留心的话,你就会发现,map方法仅仅只是创建了一个StatelessOp对象,却没有调用任何的执行代码。这其实就是流的惰性计算。任何的中间操作都没有执行任何的计算代码,只有在遇到终止操作时才会触发流的执行。接下来再看看终止操作是如何触发流的执行的,源代码:

    //类名:ReferencePipeline;方法名:forEach
    public void forEach(Consumer<? super P_OUT> action) {
    	evaluate(ForEachOps.makeRef(action, false));
    }
    
    //类名:AbstractPipeline;方法名:evaluate
    final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
    	assert getOutputShape() == terminalOp.inputShape();
    	if (linkedOrConsumed)
    		throw new IllegalStateException(MSG_STREAM_LINKED);
    	linkedOrConsumed = true;
    
    	return isParallel()
    		   ? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))
    		   : terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
    }
    
    //类名:ForEachOp;方法名:evaluateSequential
    public <S> Void evaluateSequential(PipelineHelper<T> helper,
                                               Spliterator<S> spliterator) {
    	return helper.wrapAndCopyInto(this, spliterator).get();
    }
    
    //类名:AbstractPipeline;方法名:wrapAndCopyInto。(AbstractPipeline继承了PipelineHelper)
    final <P_IN, S extends Sink<E_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator) {
    	copyInto(wrapSink(Objects.requireNonNull(sink)), spliterator); //这里的copyInto方法触发了执行
    	return sink;
    }
    
    //类名:AbstractPipeline;方法名:wrapSink
    final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) { //这个方法是关键,操作就是在这里进行连接的
    	Objects.requireNonNull(sink);
    
    	for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
    		sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
    	}
    	return (Sink<P_IN>) sink;
    }
    在wrapSink中,有一个参数需要解释一下——p.depth:这个参数表示Stream的深度,即有多少个操作需要链接。我们在创建 AbstractPipeline对象时,每进行一次中间操作对象的创建,这个属性值就会加一。如下:

    AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {
            if (previousStage.linkedOrConsumed)
                throw new IllegalStateException(MSG_STREAM_LINKED);
            previousStage.linkedOrConsumed = true;
            previousStage.nextStage = this;
    
            this.previousStage = previousStage; //前一个操作
            this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;
            this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);
            this.sourceStage = previousStage.sourceStage;
            if (opIsStateful())
                sourceStage.sourceAnyStateful = true;
            this.depth = previousStage.depth + 1; //每构造一次,就会加一
        }
    在wrapSink方法中再次调用了opWrapSink,这个方法就是前面在调用Stream的map方法时,重写的方法。如下:
    public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
            Objects.requireNonNull(mapper);
            return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
                                         StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
                @Override
                Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) { //重写方法
                    return new Sink.ChainedReference<P_OUT, R>(sink) {
                        @Override
                        public void accept(P_OUT u) {  //重写了accept方法,在方法中调用downstream的accept方法
                            downstream.accept(mapper.apply(u)); //经过这里,就可以将所有的操作连接起来
                        }
                    };
                }
            };
        }
    接下里再看看是如何触发这些操作的,触发这些操作的代码在copyInto方法中。代码如下:

     final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
            Objects.requireNonNull(wrappedSink);
    
    	if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) { //判断是否有短路特性
    		wrappedSink.begin(spliterator.getExactSizeIfKnown());
    		spliterator.forEachRemaining(wrappedSink);
    		wrappedSink.end();
    	}
    	else {
    		copyIntoWithCancel(wrappedSink, spliterator); //流有短路特性,则执行
    	}
    }
    当流没有短路特性时,调用了spliterator的forEachRemaining方法,传入了刚才包装的那个sink对象。spliteator的实现类很多,执行形式也有区别,但是在spliterator中都会调用wrapsink的accept方法。这个wrapsink的accept方法中调用downstream.accept方法,这个时候就会触发这个流的所有操作的执行。












    展开全文
  • java8新特性 lambda Stream map(函数式编程) 牛刀小试:使用Java8新特性获取股票数据https://blog.csdn.net/u014646662/article/details/82936131 Java8实战.pdf 下载:...

    java8新特性 lambda Stream map(函数式编程)

    牛刀小试:使用Java8新特性获取股票数据
    https://blog.csdn.net/u014646662/article/details/82936131

    Java8实战.pdf 下载:
    https://download.csdn.net/download/u014646662/10805079

    对人工智能感兴趣的同学,可以点击以下链接:

    现在人工智能非常火爆,很多朋友都想学,但是一般的教程都是为博硕生准备的,太难看懂了。最近发现了一个非常适合小白入门的教程,不仅通俗易懂而且还很风趣幽默。所以忍不住分享一下给大家。点这里可以跳转到教程。

    https://www.cbedai.net/u014646662

    1.接口的默认方法

    Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法

    //Formula表示一个设计 计算公式 的接口 
    public interface Formula {
    	//计算
    	double calculate(int a);
    	
    	//开方
    	default double sqrt(int a){
    		return Math.sqrt(a);
    	}
    }
    
    main:
    Formula f = new Formula() {
    	@Override
    	public double calculate(int a) {
    		return a+1;
    	}
    };
    System.out.println(f.calculate(4));
    System.out.println(f.sqrt(8));
    
    注意:现在接口还可以存在静态方法,
    可以使用 接口名.静态方法名 的形式直接调用

    2.Lambda 表达式

    2.1 认识Lambda表达式

    
    	public class LambdaTest1 {
    	
    		public static void main(String[] args) {
    			
    			//假如一个list机会中的元素要排序
    			List<String> list = Arrays.asList
    			("hello","tom","apple","bbc");
    			//之前的排序我们可以这样写
    			Collections.sort(list, new Comparator<String>(){
    				@Override
    				public int compare(String o1, String o2) {
    					return -o1.compareTo(o2);
    				}
    			});
    			
    			//使用Lambda表达式
    			Collections.sort(list,(String s1,String s2)->{
    				return s1.compareTo(s2);
    			});
    			
    			//可以简写为
    			//1.大括号里面就一句代码
    			//2.编译器可以自动推导出参数类型
    			Collections.sort(list,(s1,s2)->s1.compareTo(s2));
    			
    			System.out.println(list);
    			
    		}
    		
    	}

    2.2 Functional接口


        “函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
        我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

    public class LambdaTest2 {
    	
    		public static void main(String[] args) {
    			
    			LambdaTest2 t = new LambdaTest2();
    	//		也可以先创建对象
    	//		Action1 a1 = ()->System.out.println("hello");
    			
    			t.test1(()->System.out.println("hello"));
    			
    			//Action2<String,Integer> a2 = (f)->"这个数字是:"+f;
    			//如果参数就一个,那么还可以这样简写 去掉小括号
    			Action2<String,Integer> a2 = f->"这个数字是:"+f;
    			t.test2(a2);
    		}
    		public void test1(Action1 a){
    			a.run();
    		}
    		public void test2(Action2<String,Integer> a){
    			System.out.println(a.run(3));
    		}
    		
    	}
    	//这个注解不加也可以,加上只是为了让编译器检查
    	@FunctionalInterface
    	interface Action1{
    		public void run();
    	}
    	
    	//这个注解不加也可以,加上只是为了让编译器检查
    	@FunctionalInterface
    	interface Action2<T,F>{
    		public T run(F f);
    	}
    	
    
    	注意:lambda表达式无法访问接口的默认方法

    2.3 方法与构造函数引用

    Java 8 允许你使用 :: 关键字来传递方法(静态方法和非静态方法)

    
    	public class LambdaTest3 {
    		public static void main(String[] args) {
    			
    			LambdaTest3 t = new LambdaTest3();
    			//使用Lambda引用类的静态方法
    			//能引用Integer类中的静态方法toBinaryString的原因是:
    			//Action3接口中只有一个方法且方法的参数类型和返回值类型
    			//与Integer类中的静态方法toBinaryString的参数类型、返回类型是一致的
    			Action3 a3 = Integer::toBinaryString;
    			System.out.println(a3.run(4));
    			
    			//使用Lambda引用对象的非静态方法
    			//能引用对象t中的非静态方法test的原因是和上面的描述是一致的
    			Action3 aa3 = t::test;
    			System.out.println(aa3.run(4));
    		}
    		
    		public String test(int i){
    			return "i="+i;
    		}
    	}
    
    	@FunctionalInterface
    	interface Action3{
    		public String run(int Integer);
    	}
    
    
    	下面是一个接口中带泛型的时候特殊例子: 可以使用  类名::非静态方法  的形式引用方法
    	public class LambdaTest6 {
    	
    		public static void main(String[] args) {
    			
    			Model m = new Model();
    			
    			//方法有一个参数,然后没返回类型,这里参数类型会自动识别
    			Action<Model> a1 = (s)->System.out.println("hello");
    			a1.run(m);
    			
    			//注意:如果这里泛型类型不是Model 那么就不能引用Model中的方法
    			//可以引用Model类中任意方法 只要满足一点:该方法没有参数
    			//将来run方法中就会调用Model类型对象m的此处引用的方法
    			Action<Model> a2 = Model::test3;
    			a2.run(m);
    			
    			//引用对象m中的test2方法
    			//因为test2方法的参数和返回类型和Action接口的方法完全一致
    			Action<Model> a3 = m::test2;
    			a3.run(m);
    		}
    		
    	}
    
    	interface Action<T>{
    		public void run(T t);
    	}
    
    	class Model{
    		
    		public void test1(){
    			System.out.println("test1");
    		}
    		public void test2(Model a){
    			System.out.println("test2");
    		}
    		public int test3(){
    			System.out.println("test3");
    			return 1;
    		}
    	}
    	
    
    	Java 8 允许你使用 :: 关键字来引用构造函数
    	public class LambdaTest4 {
    		
    		public static void main(String[] args) {
    			
    			//Lambda表达式引用构造函数
    			//根据构造器的参数来自动匹配使用哪一个构造器
    			Action4Creater creater = Action4::new;
    			Action4 a4 = creater.create("zhangsan");
    			a4.say();
    			
    			
    		}
    		
    	}
    
    	class Action4{
    		private String name;
    		public Action4() {
    			
    		}
    		public Action4(String name) {
    			this.name = name;
    		}
    		public void say(){
    			System.out.println("name = "+name);
    		}
    	}
    
    	interface Action4Creater{
    		public Action4 create(String name);
    	}
    
    

        2.4 lambda表达式中的变量访问

    
    	public class LambdaTest5 {
    		private static int j;
    		private int k;
    		public static void main(String[] args) {
    			LambdaTest5 t = new LambdaTest5();
    			t.test();
    		}
    		
    		public void test(){
    			int num = 10;
    			j = 20;
    			k = 30;
    			
    			//lambda表达式中可以访问成员变量也可以方法局部变量
    			Action5 a5 = (i)->System.out.println("操作后:i="+(i+num+j+k));
    			a5.run(1);
    			
    			//但是这个被访问的变量默认变为final修饰的 不可再改变 否则编译不通过
    			//num = 60;
    			j = 50;
    			k = 70;
    		}
    		
    	}
    
    	interface Action5{
    		public void run(int i);
    	}

    2.5 Predicate接口和lambda表达式

    java.util.function.Predicate接口是用来支持java函数式编程新增的一个接口,使用这个接口和lamb表达式就可以以更少的代码为API方法添加更多的动态行为。 

    public class LambdaTest6 {
    		public static void main(String[] args) {
    			List<String> languages = Arrays.asList("Java", "html5","JavaScript", "C++", "hibernate", "PHP");
    			
    			//开头是J的语言
    			filter(languages,(name)->name.startsWith("J"));
    			//5结尾的
    			filter(languages,(name)->name.endsWith("5"));
    			//所有的语言
    			filter(languages,(name)->true);
    			//一个都不显示
    			filter(languages,(name)->false);
    			//显示名字长度大于4
    			filter(languages,(name)->name.length()>4);
    			System.out.println("-----------------------");
    			//名字以J开头并且长度大于4的
    			Predicate<String> c1 = (name)->name.startsWith("J");
    			Predicate<String> c2 = (name)->name.length()>4;
    			filter(languages,c1.and(c2));
    			
    			//名字不是以J开头
    			Predicate<String> c3 = (name)->name.startsWith("J");
    			filter(languages,c3.negate());
    			
    			//名字以J开头或者长度小于4的
    			Predicate<String> c4 = (name)->name.startsWith("J");
    			Predicate<String> c5 = (name)->name.length()<4;
    			filter(languages,c4.or(c5));
    			
    			//名字为Java的
    			filter(languages,Predicate.isEqual("Java"));
    			
    			//判断俩个字符串是否相等
    			boolean test = Predicate.isEqual("hello").test("world");
    			System.out.println(test);
    		}
    		public static void filter(List<String> languages, Predicate<String> condition) {  
    			for(String name: languages) {  
    				if(condition.test(name)) {  
    					System.out.println(name + " ");  
    				}  
    			}  
    		}  
    		
    	}

    2.6 Function 接口

     Function有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法
     compose方法表示在某个方法之前执行
    andThen方法表示在某个方法之后执行
    注意:compose和andThen方法调用之后都会把对象自己本身返回,这可以方便链式编程
    default <V> Function<T,V> andThen(Function<? super R,? extends V> after) 返回一个先执行当前函数对象apply方法再执行after函数对象apply方法的函数对象。

     default <V> Function<T,V> compose(Function<? super V,? extends T> before)返回一个先执行before函数对象apply方法再执行当前函数对象apply方法的函数对象。

     static <T> Function<T,T> identity() 返回一个执行了apply()方法之后只会返回输入参数的函数对象。

    
    	
    	
    	public interface Function<T, R> {
    
    		R apply(T t);
    
    		default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    			Objects.requireNonNull(before);
    			return (V v) -> apply(before.apply(v));
    		}
    
    		default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    			Objects.requireNonNull(after);
    			return (T t) -> after.apply(apply(t));
    		}
    		
    		//注意: t->t是(t)->t的简写
    		//t->t是作为方法identity的返回值的,也就是Function类型对象
    		//类似于这样的写法:Function<Object, Object> f = t->t;
    		//那么f.apply("test") 返回字符串"test"
    		//传入什么则返回什么
    		static <T> Function<T, T> identity() {
    			return t -> t;
    		}
    	}
    
    	例如:
    	public class LambdaTest7 {
    		//静态内部类
    		private static class Student{
    			private String name;
    			public Student(String name){
    				this.name = name;
    			}
    			public String getName() {
    				return name;
    			}
    			
    		}
    		public static void main(String[] args) {
    			/*用户注册输入一个名字tom*/
    			String name = "tom";
    			
    			/*使用用户的输入的名字创建一个对象*/
    			Function<String, Student> f1 =(s)->new Student(s);
    			//注意上面的代码也可以写出这样,引用类中的构造器
    			//Function<String, Student> f1 =Student::new;
    			Student stu1 = f1.apply(name);
    			System.out.println(stu1.getName());
    			
    			/*需求改变,使用name创建Student对象之前需要给name加一个前缀*/
    			Function<String,String> before = (s)->"briup_"+s;
    			//表示f1调用之前先执行before对象的方法,把before对象的方法返回结果作为f1对象方法的参数
    			Student stu2 = f1.compose(before).apply(name);
    			System.out.println(stu2.getName());
    			
    			/*获得创建好的对象中的名字的长度*/
    			Function<Student,Integer> after = (stu)->stu.getName().length();
    			//before先调用方法,结果作为参数传给f1来调用方法,结果再作为参数传给after,结果就是我们接收的数据
    			int len = f1.compose(before).andThen(after).apply(name);
    			System.out.println(len);
    			
    		}
    		
    	}   

        2.7 Supplier接口

    Supplier接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数

    
    	public interface Supplier<T> {
    		T get();
    	}
    	例如:
    	public class LambdaTest8 {
    		public static void main(String[] args) {
    			//生成一个八位的随机字符串
    			Supplier<String> f = ()->{
    				String base = "abcdefghijklmnopqrstuvwxyz0123456789";     
    				Random random = new Random();     
    				StringBuffer sb = new StringBuffer();     
    				for (int i = 0; i < 8; i++) {  
    					//生成[0,base.length)之间的随机数
    					int number = random.nextInt(base.length());     
    					sb.append(base.charAt(number));     
    				}     
    				return sb.toString();   
    			};
    			System.out.println(f.get());
    		}
    		
    	}

    2.8 Consumer接口

    Consumer接口接收一个任意范型的值,和Function接口不同的是该接口没有任何值

    
    	public interface Consumer<T> {
    
    		void accept(T t);
    
    		default Consumer<T> andThen(Consumer<? super T> after) {
    			Objects.requireNonNull(after);
    			return (T t) -> { accept(t); after.accept(t); };
    		}
    	}
    	例如:
    	public class LambdaTest9 {
    		//静态内部类
    		private static class Student{
    			private String name;
    
    			public String getName() {
    				return name;
    			}
    
    			public void setName(String name) {
    				this.name = name;
    			}
    		}
    		
    		public static void main(String[] args) {
    			Student s = new Student();
    			s.setName("tom");
    			
    			Consumer<Student> c = 
    			stu->System.out.println("hello!"+stu.getName());
    			c.accept(s);
    			
    		}
    		
    	}
    
    	

    总结:
            Function<T, R>  接口   R apply(T t);       有参数有返回值
            Supplier<T>       接口   T get();          没参数有返回值
            Consumer<T>    接口   void accept(T t); 有参数没返回值

            另外需要注意的接口: 其用法和上面介绍的接口使用方式类同
            BinaryOperator<T>接口    T apply(T t, T t)  将两个T作为输入,返回一个T作为输出
            BiFunction<T, U, R>接口  R apply(T t, U u)  将一个T和一个U输入,返回一个R作为输出
            BinaryOperator接口继承了BiFunction接口
            public interface BinaryOperator<T> extends BiFunction<T,T,T>

            BiConsumer<T, U>接口  void accept(T t, U u) 将俩个参数传入,没有返回值

    2.9 Optional类

        Optional 不是接口而是一个类,这是个用来防止NullPointerException异常的辅助类型
        Optional 被定义为一个简单的容器,其值可能是null或者不是null。
        在Java8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。
        这是一个可以为null的容器对象。
        如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

    
    public class Optotion {
    
    public static void main(String[] args) {
    	
    	/*of方法 为非null的值创建一个Optional*/
    	//of方法通过工厂方法创建Optional类。
    	//需要注意的是,创建对象时传入的参数不能为null。
    	//如果传入参数为null,则抛出NullPointerException 。
    	Optional<String> op1 = Optional.of("hello");
    	
    	/*ofNullable方法 为指定的值创建一个Optional,如果指定的值为null,则返回一个空的Optional。*/
    	//ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况
    	Optional<String> op2 = Optional.ofNullable(null);
    	
    	/*isPresent方法 如果值存在返回true,否则返回false。*/
    	/*get方法 如果Optional有值则将其返回,否则抛出NoSuchElementException。*/
    	if(op1.isPresent()){
    		System.out.println(op1.get());
    	}
    	if(op2.isPresent()){
    		System.out.println(op2.get());
    	}
    	
    	/*ifPresent方法 如果Optional实例有值则为其调用consumer,否则不做处理*/
    	//consumer接口中的方法只有参数没有返回值
    	op1.ifPresent(str->System.out.println(str));
    	op2.ifPresent(str->System.out.println(str));//这个不执行 因为op2里面的值是null
    	
    	
    	/*orElse方法 如果有值则将其返回,否则返回指定的其它值。*/
    	System.out.println(op1.orElse("如果op1中的值为null则返回这句话,否则返回这个值"));
    	System.out.println(op2.orElse("如果op2中的值为null则返回这句话,否则返回这个值"));
    	
    	
    	/*orElseGet方法 orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值。*/
    	//Supplier接口中的方法没有参数但是有返回值
    	System.out.println(op1.orElseGet(()->"自己定义的返回值"));
    	System.out.println(op2.orElseGet(()->"自己定义的返回值"));
    	
    	
    	/*orElseThrow方法 如果有值则将其返回,否则抛出supplier接口创建的异常。*/
    	//在orElseThrow中我们可以传入一个lambda表达式或方法,如果值不存在来抛出异常。
    	//orElseThrow方法的声明如下 所有只能返回一个Throwable类型对象
    	//public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
    	try {
    		System.out.println(op1.orElseThrow(Exception::new));;
    		//System.out.println(op2.orElseThrow(Exception::new));;这个会抛出异常
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    	
    	
    	/*map方法 如果有值,则对其执行调用mapper函数得到返回值。*/
    	//返回值并且依然Optional包裹起来,其泛型和你返回值的类型一致
    	//public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
    	Optional<Integer> map1 = op1.map(str->1);
    	System.out.println(map1.get());
    	Optional<Double> map2 = op2.map(str->1.2);
    	System.out.println(map2.orElse(0.0));
    	
    	
    	/*flatMap方法 如果有值,为其执行mapper函数返回Optional类型返回值,否则返回空Optional。*/
    	//flatMap与map方法类似,区别在于flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装。
    	//需要我们自己把返回值封装为Optional
    	//public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) 
    	System.out.println(op1.flatMap(str->Optional.of(str+"_briup")).get());
    	//op1.flatMap(str->"");编译出错
    	
    	
    	/*filter方法 如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional。*/
    	//public Optional<T> filter(Predicate<? super T> predicate) 
    	op1 = op1.filter(str->str.length()<10);
    	System.out.println(op1.orElse("值为null"));
    	op1 = op1.filter(str->str.length()>10);
    	System.out.println(op1.orElse("值为null"));
    }

    2.10 Stream 接口

    java.util.Stream 表示能应用在一组元素上一次执行的操作序列。
        Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,
        而中间操作返回Stream本身,这样你就可以将多个操作依次串起来(链式编程)。
        Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。
        Stream的操作可以串行执行或者并行执行。
        Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。
        Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、
        高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。
        Stream API 借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。
        同时它提供串行和并行两种模式进行汇聚操作

    2.10.1 Stream对象的构建:

    
    // 1.使用值构建
    Stream<String> stream = Stream.of("a", "b", "c");
    // 2. 使用数组构建
    String[] strArray = new String[] {"a", "b", "c"};
    Stream<String> stream = Stream.of(strArray);
    Stream<String> stream = Arrays.stream(strArray);
    // 3. 利用集合构建(不支持Map集合)
    List<String> list = Arrays.asList(strArray);
    stream = list.stream();

    对于基本数值型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。
    当然我们也可以用 Stream<Integer>、Stream<Long> 、Stream<Double>,但是 自动拆箱装箱会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。
    Java 8 中还没有提供其它基本类型数值的Stream

    2.10.2 数值Stream的构建:

    IntStream stream1 = IntStream.of(new int[]{1, 2, 3});
    //[1,3)
    IntStream stream2 = IntStream.range(1, 3);
    //[1,3]
    IntStream stream3 = IntStream.rangeClosed(1, 3);

    2.10.3 Stream转换为其它类型:

    
    Stream<String> stream = Stream.of("hello","world","tom");
    // 1. 转换为Array
    String[] strArray  = stream.toArray(String[]::new);
    // 2. 转换为Collection
    List<String> list1 = stream.collect(Collectors.toList());
    List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
    Set<String> set3 = stream.collect(Collectors.toSet());
    Set<String> set4 = stream.collect(Collectors.toCollection(HashSet::new));
    // 3. 转换为String
    String str = stream.collect(Collectors.joining()).toString();

    特别注意 : 一个 Stream 只可以使用一次,上面的代码为了简洁而重复使用了多次。
    这个代码直接运行会抛出异常的:
    java.lang.IllegalStateException: stream has already been operated upon or closed


    2.10.4 Stream操作
    当把一个数据结构包装成Stream后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。

    Intermediate:中间操作
    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

    Terminal: 最终操作
    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、findFirst、 findAny、 iterator

    Short-circuiting: 短路操作
    anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

    map/flatMap映射 把 Stream中 的每一个元素,映射成另外一个元素。

    //转换大写
    Stream<String> wordList = Stream.of("hello","world","tom");
    List<String> output = wordList. map(String::toUpperCase). collect(Collectors.toList());
      //也可以直接使用forEach循环输出
    wordList.map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);
    
    
    //计算平方数
    List<Integer> nums = Arrays.asList(1, 2, 3, 4);
    List<Integer> squareNums =  nums.stream(). map(n -> n * n). collect(Collectors.toList());


    map生成的是个1:1映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。
    map和flatMap的方法声明是不一样的
    <R> Stream<R>      map(Function<? super T, ? extends R> mapper);
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

    //stream1中的每个元素都是一个List集合对象
    Stream<List<Integer>> stream1 = Stream.of(
    				 Arrays.asList(1),
    				 Arrays.asList(2, 3),
    				 Arrays.asList(4, 5, 6)
    			 );
    			Stream<Integer> stream2 = stream1.
    			flatMap((e) -> e.stream());
    			
    stream2.forEach(e->System.out.println(e));//输出1 2 3 4 5 6
    flatMap 把 stream1 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终新的 stream2 里面已经没有 List 了,都是直接的数字。
    
    例子:
    Stream<String> stream1 = Stream.of("tom.Li","lucy.Liu");
    //flatMap方法把stream1中的每一个字符串都用[.]分割成了俩个字符串
    //最后返回了一个包含4个字符串的stream2
    Stream<String> stream2 = stream1.flatMap(s->Stream.of(s.split("[.]")));
    stream2.forEach(System.out::println);
    输出结果:
    	tom
    	Li
    	lucy
    	Liu
    

    forEach 遍历 接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。
    forEach 是 terminal 操作,执行完stream就不能再用了

    List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
    list.stream().forEach(System.out::println);

    filter 过滤 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。
    通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作

    List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
    list.stream().filter(s->s.length()>4).forEach(System.out::println);
    注意:System.out::println 这个是lambda表达式中对静态方法的引用

    peek 对每个元素执行操作并返回一个新的 Stream
    注意:调用peek之后,一定要有一个最终操作
    peek是一个intermediate 操作

    List<String> list = Arrays.asList("one", "two", "three", "four");
    List<String> list2 = list.stream()
    			 .filter(e -> e.length() > 3)
    			 .peek(e -> System.out.println("第一次符合条件的值为: " + e))
    			 .filter(e->e.length()>4)
    			 .peek(e -> System.out.println("第二次符合条件的值为: " + e))
    			 .collect(Collectors.toList());
    System.out.println(list2.size());//打印结果为 1
    最后list2中就存放的筛选出来的元素

    findFirst 总是返回 Stream 的第一个元素,或者空,返回值类型:Optional。
    如果集中什么都没有,那么list.stream().findFirst()返回一个Optional<String>对象,但是里面封装的是一个null。

    List<String> list = Arrays.asList("test","hello","world");
    Optional<String> first = list.stream().findFirst();
    System.out.println(first.orElse("值为null"));

    sort 排序
    排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
    对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。

    List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
    list.stream().sorted().filter(s->s.startsWith("j")).forEach(System.out::println);
    //按照字符串的长短排序
    list.stream().sorted((s1,s2)->s1.length()-s2.length()).forEach(System.out::println);

    需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据list是不会被修改的

    map 映射
    中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。 你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。

    List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
    list.stream().map(s->s.toUpperCase()).forEach(System.out::println);

    Match 匹配
    Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。

    
    //所有元素匹配成功才返回true 否则返回false
    例子:
    List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
    boolean allMatch = list.stream().allMatch((s)->s.startsWith("j"));
    System.out.println(allMatch);
    
    //任意一个匹配成功就返回true 否则返回false
    例子:
    List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
    boolean anyMatch = list.stream().anyMatch((s)->s.startsWith("j"));
    System.out.println(anyMatch);
    
    //没有一个匹配的就返回true 否则返回false
    例子:
    List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
    boolean noneMatch = list.stream().noneMatch((s)->s.startsWith("j"));
    System.out.println(noneMatch);

    Count 计数
    计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。

    List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
    long count = list.stream().filter(s->s.startsWith("j")).count();
    System.out.println(count);

    Reduce 规约/合并
    这是一个最终操作,允许通过指定的函数来将stream中的多个元素规约合并为一个元素.它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。Stream.reduce,常用的方法有average, sum, min, max, and count,返回单个的结果值, 并且reduce操作每处理一个元素总是创建一个新值.
    从这个意义上说,字符串拼接、数值的 sum、min、max等都是特殊的 reduce。 

    IntStream integers = IntStream.range(1, 10);
    Integer sum = integers.reduce(0, (a, b) -> a+b); 或
    Integer sum = integers.reduce(0, Integer::sum);
    //也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。
    OptionalInt min = integers.reduce((a, b) -> a<b?a:b);
    // 字符串连接,concat = "ABCD"
    String concat 		 = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
    Optional<String> opStr = Stream.of("A", "B", "C", "D").reduce(String::concat); 
    
    List<String> list =Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
    Optional<String> reduce = list.stream()
        .sorted((s1,s2)->s2.length()-s1.length())
        .filter(s->s.startsWith("j"))
        .map(s->s+"_briup")
        .reduce((s1,s2)->s1+"|"+s2);
    System.out.println(reduce.orElse("值为空"));//打印结果为: javascript_briup|javap_briup|java_briup
    整个代码有点长,可以换行看下:
    Optional<String> reduce    =  list.stream()
    				  .sorted((s1,s2)->s2.length()-s1.length())
    				  .filter(s->s.startsWith("j"))
    				  .map(s->s+"_briup")
    				  .reduce((s1,s2)->s1+"|"+s2);

        1.先调用stream方法
        2.再排序,按照字符串的长度进行排序,长的在前短的再后
        3.再过滤,字符串必须是以字符'j'开头的
        4.再进行映射,把每个字符串后面拼接上"_briup"
        5.再调用reduce进行合并数据,使用"|"连接字符串
        6.最后返回Optional<String>类型数据,处理好的字符串数据就封装在这个对象中  

    limit/skip 
    limit 返回 Stream 的前面 n 个元素;skip 则是跳过前 n 个元素只要后面的元素

    List<String> list = Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
    list.stream().limit(5).forEach(System.out::println);
    list.stream().skip(5).forEach(System.out::println);

    min/max/distinct

    //找出字符文件中字符字符最长的一行
    BufferedReader br = new BufferedReader(new FileReader("src/com/briup/test/a.txt"));
    int maxLen = br.lines().
    	   	mapToInt(String::length).
    	   	max().
    	   	getAsInt();
    
    System.out.println(maxLen);	

    注意:lines方法把文件中所有行都返回并且转换为一个Stream<String>类型对象,因为每行读出的String类型数据,同时String::length是使用方法引用的特殊方式(因为泛型的缘故),上面的笔记中已经介绍过了,max()方法执行后返回的时候OptionalInt类型对象,所以接着调用了getAsInt方法来获得这次运行结果的int值

    //找出全文的单词,转小写,去掉空字符,去除重复单词并排序
    BufferedReader br = new BufferedReader(new FileReader("src/com/briup/test4/day17.txt"));
    br.lines().
       flatMap(s->Stream.of(s.split(" "))).
       filter(s->s.length()>0).
       map(s->s.toLowerCase()).
       distinct().
       sorted().
       forEach(System.out::println);
    Stream.generate
    通过Supplier接口,可以自己来控制Stream的生成。这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。
    把 Supplier 实例传递给 Stream.generate() 生成的 Stream,由于它是无限的,在管道中,必须利用limit之类的操作限制Stream大小。可以使用此方式制造出海量的测试数据
    public static<T> Stream<T> generate(Supplier<T> s);
    例子:
    生成100个随机数并由此创建出Stream实例
    Stream.generate(()->(int)(Math.random()*100)).limit(100).forEach(System.out::println);
    	
    Stream.iterate
    iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(假设是 f)。
    然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,
    	f(f(f(seed))) 第四个,以此类推。
    该方法的声明为:
    public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
    
    UnaryOperator接口继承了Function接口:
    public interface UnaryOperator<T> extends Function<T, T>
    例子:
    生成一个等差数列
    Stream.iterate(0, n -> n + 3).
    			limit(10). 
    			forEach(x -> System.out.print(x + " "));
    打印结果:
    0 3 6 9 12 15 18 21 24 27 

    Collectors 
    java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的操作。
    例如把Stream转变输出为 Collection,或者把 Stream 元素进行分组。

    //把Stream中的元素进行过滤然后再转为List集合
    List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
    List<String> result = list.stream().filter(s->s.length()>4).collect(Collectors.toList());
    
    //分组:按照字符串的长度分组
    List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
    //相同长度的字符串放到一个List集合中作为Map的value,字符串的长度作为Map的Key
    Map<Integer, List<String>> collect = list.stream().collect(Collectors.groupingBy(String::length));
    //注意下面写法可能写到s->s.length()的时候Eclipse里面有可能不会代码提示,这个要看你先的是=号的哪一边
    //最终原因还是泛型的事情
    Map<Integer, List<String>> collect = list.stream().collect(Collectors.groupingBy(s->s.length()));
    	
    //分割:按照字符串是否包含java进行划分  partitioning分割划分的意思
    Map<Boolean, List<String>> collect = 
    		list.stream().collect(Collectors.partitioningBy(s->s.indexOf("java")!=-1));
    for(Boolean b:collect.keySet()){
    	System.out.println(b+" : "+collect.get(b).size());
    }

    2.11 并行Streams

    Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。

    
    	public class LambdaTest12 {
    	
    		public static void main(String[] args) {
    			
    			//生成100万个不同的字符串放到集合中
    			int max = 1000000;
    			List<String> values = new ArrayList<String>(max);
    			for (int i = 0; i < max; i++) {
    			    UUID uuid = UUID.randomUUID();
    			    values.add(uuid.toString());
    			}
    
    
    			//1纳秒*10^9=1秒 
    			long t0 = System.nanoTime();
    			//串行stream 
    			long count = values.stream().sorted().count();
    			//并行stream
    			//long count = values.parallelStream().sorted().count();
    			long t1 = System.nanoTime();
    
    			long time = t1 - t0;
    			System.out.println(count);
    			System.out.println(time);
    		}
    		
    	}
    
    	结论:对100万个字符串进行排序和计数操作,串行和并行运算的用时差别还是很明显的
    
    
    
    
    
    
    
    

    2.12 Map集合
        Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。
        Java8为Map新增的方法:

    Object compute(Object key, BiFunction remappingFunction):
    该方法使用remappingFunction根据原key-value对计算一个新的value。
    只要新的value不为null,就使用新的value覆盖原value;如果新的value为null,则删除原key-value对;
    
    Object computeIfAbsent(Object key, Function mappingFunction):
    如果传入的key参数在Map中对应的value为null,
    该方法将使用mappingFunction根据原key、value计算一个新的结果,则用该计算结果覆盖原value;
    如果传入的key参数在Map中对应的value为null,则该方法不做任何事情;如果原Map原来不包括该key,
    该方法可能会添加一组key-value对。
    
    Object computeIfPresent(Object key, BiFunction remappingFunction):
    如果传给该方法的key参数在Map中对应的value不为null,
    该方法将使用remappingFunction根据原key、value计算一个新结果,并且该计算结果不为null,
    则使用该结果覆盖原来的value;
    如果计算结果为null,则删除原key-value对。
    
    void forEach(BiConsumer action):
    该方法是Java8为Map新增的一个遍历key-value对的方法。
    
    Object getOrDefault(Object key, V defaultValue):
    获取指定的key对应的value。如果该key不存在,则返回defaultValue。
    
    Object merge(Object key, Object value, BiFunction remappingFunction):
    该方法会先根据key参数获取该Map中对应的value。如果获取的value为null,
    则直接使用传入的value覆盖原value(在这种情况下,可能会添加一组key-value);
    如果获取的value不为null,则使用remappingFunction函数根据原value、新value计算一个新的结果,并用新的结果去覆盖原有的value。
    
    Object putIfAbsent(Object key, Object value):
    该方法会自动检测指定的key对应的value是否为null,如果该key对应的value为null,则使用传入的新value代替原来的null。
    如果该key对应的value不是null,那么该方法不做任何事情。
    
    Object replace(Object key, Object value):
    将Map中指定key对应的value替换成新value并把被替换掉的旧值返回。
    如果key在Map中不存在,该方法不会添加key-value对,而是返回null。
    
    Boolean replace(K key, V oldValue, V newValue):
    将Map中指定的key-value对的原value替换成新value。
    如果在Map中找到指定的key-value对,则执行替换并返回true,否则返回false。
    
    replaceAll(BiFunction function):
    该方法使用function对原key-value对执行计算,并将计算结果作为key-value对的value值

     

    展开全文
  • JDK1.8-Stream()使用详解

    2017-08-06 21:27:18
    为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的 ...

    为什么需要 Stream

    Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的 Stream。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。

    什么是聚合操作

    在传统的 J2EE 应用中,Java 代码经常不得不依赖于关系型数据库的聚合操作来完成诸如:

    • 客户每月平均消费金额
    • 最昂贵的在售商品
    • 本周完成的有效订单(排除了无效的)
    • 取十个数据样本作为首页推荐

    这类的操作。

    但在当今这个数据大爆炸的时代,在数据来源多样化、数据海量化的今天,很多时候不得不脱离 RDBMS,或者以底层返回的数据为基础进行更上层的数据统计。而 Java 的集合 API 中,仅仅有极少量的辅助型方法,更多的时候是程序员需要用 Iterator 来遍历集合,完成相关的聚合应用逻辑。这是一种远不够高效、笨拙的方法。在 Java 7 中,如果要发现 type 为 grocery 的所有交易,然后返回以交易值降序排序好的交易 ID 集合,我们需要这样写:

    清单 1. Java 7 的排序、取值实现

    List<Transaction> groceryTransactions = new Arraylist<>();
    for(Transaction t: transactions){
     if(t.getType() == Transaction.GROCERY){
     groceryTransactions.add(t);
     }
    }
    Collections.sort(groceryTransactions, new Comparator(){
     public int compare(Transaction t1, Transaction t2){
     return t2.getValue().compareTo(t1.getValue());
     }
    });
    List<Integer> transactionIds = new ArrayList<>();
    for(Transaction t: groceryTransactions){
     transactionsIds.add(t.getId());
    }

    而在 Java 8 使用 Stream,代码更加简洁易读;而且使用并发模式,程序执行速度更快。

    清单 2. Java 8 的排序、取值实现

    List<Integer> transactionsIds = transactions.parallelStream().
     filter(t -> t.getType() == Transaction.GROCERY).
     sorted(comparing(Transaction::getValue).reversed()).
     map(Transaction::getId).
     collect(toList());

    Stream 总览

    什么是流

    Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

    Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

    而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。Java 的并行 API 演变历程基本如下:

    >
    1. 1.0-1.4 中的 java.lang.Thread
    2. 5.0 中的 java.util.concurrent
    02. 6.0 中的 Phasers 等
    10. 7.0 中的 Fork/Join 框架
    1. 8.0 中的 Lambda

    Stream 的另外一大特点是,数据源本身可以是无限的。

    流的构成

    当我们使用一个流的时候,通常包括三个基本步骤:

    获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示。

    图 1. 流管道 (Stream Pipeline) 的构成

    有多种方式生成 Stream Source:

    • 从 Collection 和数组

      • Collection.stream()
      • Collection.parallelStream()
      • Arrays.stream(T array) or Stream.of()

      从 BufferedReader

      • java.io.BufferedReader.lines()
    • 静态工厂
      • java.util.stream.IntStream.range()
      • java.nio.file.Files.walk()
    • 自己构建

      • java.util.Spliterator
      • 其它
        • Random.ints()
        • BitSet.stream()
        • Pattern.splitAsStream(java.lang.CharSequence)
        • JarFile.stream()

    流的操作类型分为两种:

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

    在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

    还有一种操作被称为 short-circuiting。用以指:

    • 对于一个intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的Stream,但返回一个有限的新Stream
    • 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

    当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。

    清单 3. 一个流操作的示例

    int sum = widgets.stream()
    .filter(w -> w.getColor() == RED)
     .mapToInt(w -> w.getWeight())
     .sum();

    stream() 获取当前小物件的 source,filter 和 mapToInt 为 intermediate 操作,进行数据筛选和转换,最后一个 sum() 为 terminal 操作,对符合条件的全部小物件作重量求和。

    流的使用详解

    简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)。

    流的构造与转换

    下面提供最常见的几种构造 Stream 的样例。

    清单 4. 构造流的几种常见方法

    // 1. Individual values
    Stream stream = Stream.of("a", "b", "c");
    // 2. Arrays
    String [] strArray = new String[] {"a", "b", "c"};
    stream = Stream.of(strArray);
    stream = Arrays.stream(strArray);
    // 3. Collections
    List<String> list = Arrays.asList(strArray);
    stream = list.stream();

    需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:

    IntStream、LongStream、DoubleStream。当然我们也可以用 Stream<Integer>、Stream<Long> >、Stream<Double>,但是 boxingunboxing 会很耗时,所以特别为这三种基本数值型提供了对应的Stream
    Java 8 中还没有提供其它数值型 Stream,因为这将导致扩增的内容较多。而常规的数值型聚合运算可以通过上面三种 Stream 进行。

    清单 5. 数值流的构造

    IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
    IntStream.range(1, 3).forEach(System.out::println);
    IntStream.rangeClosed(1, 3).forEach(System.out::println);

    清单 6. 流转换为其它数据结构

    // 1. Array
    String[] strArray1 = stream.toArray(String[]::new);
    // 2. Collection
    List<String> list1 = stream.collect(Collectors.toList());
    List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
    Set set1 = stream.collect(Collectors.toSet());
    Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
    // 3. String
    String str = stream.collect(Collectors.joining()).toString();

    一个 Stream 只可以使用一次,上面的代码为了简洁而重复使用了数次。

    流的操作

    接下来,当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。

    • Intermediate
      • map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
    • Terminal
      • forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
    • Short-circuiting
      • anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

    我们下面看一下 Stream 的比较典型用法。

    map/flatMap


    我们先来看 map。如果你熟悉 scala 这类函数式语言,对这个方法应该很了解,它的作用就是把 inputStream 的每一个元素,映射成 outputStream 的另外一个元素。

    清单 7. 转换大写

    List<String> output = wordList.stream().
    map(String::toUpperCase).
    collect(Collectors.toList());

    这段代码把所有的单词转换为大写。

    清单 8. 平方数

    List<Integer> nums = Arrays.asList(1, 2, 3, 4);
    List<Integer> squareNums = nums.stream().
    map(n -> n * n).
    collect(Collectors.toList());

    这段代码生成一个整数 list 的平方数 {1, 4, 9, 16}。

    从上面例子可以看出,map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。

    清单 9. 一对多

    Stream<List<Integer>> inputStream = Stream.of(
     Arrays.asList(1),
     Arrays.asList(2, 3),
     Arrays.asList(4, 5, 6)
     );
    Stream<Integer> outputStream = inputStream.
    flatMap((childList) -> childList.stream());

    flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。

    filter


    filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。

    清单 10. 留下偶数

    Integer[] sixNums = {1, 2, 3, 4, 5, 6};
    Integer[] evens =
    Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);

    经过条件“被 2 整除”的 filter,剩下的数字为 {2, 4, 6}。

    清单 11. 把单词挑出来

    List<String> output = reader.lines().
     flatMap(line -> Stream.of(line.split(REGEXP))).
     filter(word -> word.length() > 0).
     collect(Collectors.toList());

    这段代码首先把每行的单词用 flatMap 整理到新的 Stream,然后保留长度不为 0 的,就是整篇文章中的全部单词了。

    forEach


    forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。

    清单 12. 打印姓名(forEach 和 pre-java8 的对比)

    // Java 8
    roster.stream()
     .filter(p -> p.getGender() == Person.Sex.MALE)
     .forEach(p -> System.out.println(p.getName()));
    // Pre-Java 8
    for (Person p : roster) {
     if (p.getGender() == Person.Sex.MALE) {
     System.out.println(p.getName());
     }
    }

    对一个人员集合遍历,找出男性并打印姓名。可以看出来,forEach 是为 Lambda 而设计的,保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的,非常方便。当需要为多核系统优化时,可以 parallelStream().forEach(),只是此时原有元素的次序没法保证,并行的情况下将改变串行时操作的行为,此时 forEach 本身的实现不需要调整,而 Java8 以前的 for 循环 code 可能需要加入额外的多线程逻辑。

    但一般认为,forEach 和常规 for 循环的差异不涉及到性能,它们仅仅是函数式风格与传统 Java 风格的差别。

    另外一点需要注意,forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,你无法对一个 Stream 进行两次 terminal 运算。下面的代码是错误的:

    stream.forEach(element -> doOneThing(element));
    stream.forEach(element -> doAnotherThing(element));

    相反,具有相似功能的 intermediate 操作 peek 可以达到上述目的。如下是出现在该 api javadoc 上的一个示例。

    清单 13. peek 对每个元素执行操作并返回一个新的 Stream

    Stream.of("one", "two", "three", "four")
     .filter(e -> e.length() > 3)
     .peek(e -> System.out.println("Filtered value: " + e))
     .map(String::toUpperCase)
     .peek(e -> System.out.println("Mapped value: " + e))
     .collect(Collectors.toList());

    forEach 不能修改自己包含的本地变量值,也不能用 break/return 之类的关键字提前结束循环。

    findFirst


    这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。

    这里比较重点的是它的返回值类型:Optional。这也是一个模仿 Scala 语言中的概念,作为一个容器,它可能含有某值,或者不包含。使用它的目的是尽可能避免 NullPointerException。

    清单 14. Optional 的两个用例

    String strA = " abcd ", strB = null;
    print(strA);
    print("");
    print(strB);
    getLength(strA);
    getLength("");
    getLength(strB);
    public static void print(String text) {
     // Java 8
     Optional.ofNullable(text).ifPresent(System.out::println);
     // Pre-Java 8
     if (text != null) {
     System.out.println(text);
     }
     }
    public static int getLength(String text) {
     // Java 8
    return Optional.ofNullable(text).map(String::length).orElse(-1);
     // Pre-Java 8
    // return if (text != null) ? text.length() : -1;
     };

    在更复杂的 if (xx != null) 的情况中,使用 Optional 代码的可读性更好,而且它提供的是编译时检查,能极大的降低 NPE 这种 Runtime Exception 对程序的影响,或者迫使程序员更早的在编码阶段处理空值问题,而不是留到运行时再发现和调试。

    Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average() 返回 OptionalDouble 等等。

    reduce


    这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于Integer sum = integers.reduce(0, (a, b) -> a+b);Integer sum = integers.reduce(0, Integer::sum);

    也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。

    清单 15. reduce 的用例

    // 字符串连接,concat = "ABCD"
    String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
    // 求最小值,minValue = -3.0
    double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
    // 求和,sumValue = 10, 有起始值
    int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
    // 求和,sumValue = 10, 无起始值
    sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
    // 过滤,字符串连接,concat = "ace"
    concat = Stream.of("a", "B", "c", "D", "e", "F").
     filter(x -> x.compareTo("Z") > 0).
     reduce("", String::concat);

    上面代码例如第一个示例的 reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。

    limit/skip


    limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素(它是由一个叫 subStream 的方法改名而来)。

    清单 16. limit 和 skip 对运行次数的影响

    public void testLimitAndSkip() {
     List<Person> persons = new ArrayList();
     for (int i = 1; i <= 10000; i++) {
     Person person = new Person(i, "name" + i);
     persons.add(person);
     }
    List<String> personList2 = persons.stream().
    map(Person::getName).limit(10).skip(3).collect(Collectors.toList());
     System.out.println(personList2);
    }
    private class Person {
     public int no;
     private String name;
     public Person (int no, String name) {
     this.no = no;
     this.name = name;
     }
     public String getName() {
     System.out.println(name);
     return name;
     }
    }

    输出结果为:

    name1
    name2
    name3
    name4
    name5
    name6
    name7
    name8
    name9
    name10
    [name4, name5, name6, name7, name8, name9, name10]

    这是一个有 10,000 个元素的 Stream,但在 short-circuiting 操作 limit 和 skip 的作用下,管道中 map 操作指定的 getName() 方法的执行次数为 limit 所限定的 10 次,而最终返回结果在跳过前 3 个元素后只有后面 7 个返回。
    有一种情况是 limit/skip 无法达到 short-circuiting 目的的,就是把它们放在 Stream 的排序操作后,原因跟 sorted 这个 intermediate 操作有关:此时系统并不知道 Stream 排序后的次序如何,所以 sorted 中的操作看上去就像完全没有被 limit 或者 skip 一样。

    清单 17. limit 和 skip 对 sorted 后的运行次数无影响

    List<Person> persons = new ArrayList();
     for (int i = 1; i <= 5; i++) {
     Person person = new Person(i, "name" + i);
     persons.add(person);
     }
    List<Person> personList2 = persons.stream().sorted((p1, p2) -> 
    p1.getName().compareTo(p2.getName())).limit(2).collect(Collectors.toList());
    System.out.println(personList2);

    上面的示例对清单 13 做了微调,首先对 5 个元素的 Stream 排序,然后进行 limit 操作。输出结果为:

    name2
    name1
    name3
    name2
    name4
    name3
    name5
    name4
    [stream.StreamDW$Person@816f27d, stream.StreamDW$Person@87aac27]

    即虽然最后的返回元素数量是 2,但整个管道中的 sorted 表达式执行次数没有像前面例子相应减少。

    最后有一点需要注意的是,对一个 parallel 的 Steam 管道来说,如果其元素是有序的,那么 limit 操作的成本会比较大,因为它的返回对象必须是前 n 个也有一样次序的元素。取而代之的策略是取消元素间的次序,或者不要用 parallel Stream。

    sorted


    对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。我们对清单 14 进行优化:

    清单 18. 优化:排序前进行 limit 和 skip

    List<Person> persons = new ArrayList();
     for (int i = 1; i <= 5; i++) {
     Person person = new Person(i, "name" + i);
     persons.add(person);
     }
    List<Person> personList2 = persons.stream().limit(2).sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
    System.out.println(personList2);

    结果会简单很多:

    name2
    name1
    [stream.StreamDW$Person@6ce253f1, stream.StreamDW$Person@53d8d10a]

    当然,这种优化是有 business logic 上的局限性的:即不要求排序后再取值。
    min/max/distinct


    min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现,但前者的性能会更好,为 O(n),而 sorted 的成本是 O(n log n)。同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作。

    清单 19. 找出最长一行的长度

    BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log"));
    int longest = br.lines().
     mapToInt(String::length).
     max().
     getAsInt();
    br.close();
    System.out.println(longest);

    下面的例子则使用 distinct 来找出不重复的单词。

    清单 20. 找出全文的单词,转小写,并排序

    List<String> words = br.lines().
     flatMap(line -> Stream.of(line.split(" "))).
     filter(word -> word.length() > 0).
     map(String::toLowerCase).
     distinct().
     sorted().
     collect(Collectors.toList());
    br.close();
    System.out.println(words);

    Match


    Stream 有三个 match 方法,从语义上说:

    • allMatch:Stream 中全部元素符合传入的 predicate,返回 true
    • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
    • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
      它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。对清单 13 中的 Person 类稍做修改,加入一个 age 属性和 getAge 方法。

    清单 21. 使用 Match

    List<Person> persons = new ArrayList();
    persons.add(new Person(1, "name" + 1, 10));
    persons.add(new Person(2, "name" + 2, 21));
    persons.add(new Person(3, "name" + 3, 34));
    persons.add(new Person(4, "name" + 4, 6));
    persons.add(new Person(5, "name" + 5, 55));
    boolean isAllAdult = persons.stream().
     allMatch(p -> p.getAge() > 18);
    System.out.println("All are adult? " + isAllAdult);
    boolean isThereAnyChild = persons.stream().
     anyMatch(p -> p.getAge() < 12);
    System.out.println("Any child? " + isThereAnyChild);

    输出结果:

    All are adult? false
    Any child? true

    进阶:自己生成流

    Stream.generate


    通过实现 Supplier 接口,你可以自己来控制流的生成。这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。由于它是无限的,在管道中,必须利用 limit 之类的操作限制 Stream 大小。

    清单 22. 生成 10 个随机整数

    Random seed = new Random();
    Supplier<Integer> random = seed::nextInt;
    Stream.generate(random).limit(10).forEach(System.out::println);
    //Another way
    IntStream.generate(() -> (int) (System.nanoTime() % 100)).
    limit(10).forEach(System.out::println);

    Stream.generate() 还接受自己实现的 Supplier。例如在构造海量测试数据的时候,用某种自动的规则给每一个变量赋值;或者依据公式计算 Stream 的每个元素值。这些都是维持状态信息的情形。

    清单 23. 自实现 Supplier

    Stream.generate(new PersonSupplier()).
    limit(10).
    forEach(p -> System.out.println(p.getName() + ", " + p.getAge()));
    private class PersonSupplier implements Supplier<Person> {
     private int index = 0;
     private Random random = new Random();
     @Override
     public Person get() {
     return new Person(index++, "StormTestUser" + index, random.nextInt(100));
     }
    }

    输出结果:

    StormTestUser1, 9
    StormTestUser2, 12
    StormTestUser3, 88
    StormTestUser4, 51
    StormTestUser5, 22
    StormTestUser6, 28
    StormTestUser7, 81
    StormTestUser8, 51
    StormTestUser9, 4
    StormTestUser10, 76

    iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(例如 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推。

    清单 24. 生成一个等差数列

    Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));

    输出结果:

    0 3 6 9 12 15 18 21 24 27

    与 Stream.generate 相仿,在 iterate 时候管道必须有 limit 这样的操作来限制 Stream 大小。
    进阶:用 Collectors 来进行 reduction 操作
    java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。
    groupingBy/partitioningBy

    清单 25. 按照年龄归组

    Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()).
     limit(100).
     collect(Collectors.groupingBy(Person::getAge));
    Iterator it = personGroups.entrySet().iterator();
    while (it.hasNext()) {
     Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
     System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
    }

    上面的 code,首先生成 100 人的信息,然后按照年龄归组,相同年龄的人放到同一个 list 中,可以看到如下的输出:

    Age 0 = 2
    Age 1 = 2
    Age 5 = 2
    Age 8 = 1
    Age 9 = 1
    Age 11 = 2
    ……

    清单 26. 按照未成年人和成年人归组

    Map<Boolean, List<Person>> children = Stream.generate(new PersonSupplier()).
     limit(100).
     collect(Collectors.partitioningBy(p -> p.getAge() < 18));
    System.out.println("Children number: " + children.get(true).size());
    System.out.println("Adult number: " + children.get(false).size());

    输出结果:

    Children number: 23 
    Adult number: 77

    在使用条件“年龄小于 18”进行分组后可以看到,不到 18 岁的未成年人是一组,成年人是另外一组。partitioningBy 其实是一种特殊的 groupingBy,它依照条件测试的是否两种结果来构造返回的数据结构,get(true) 和 get(false) 能即为全部的元素对象。

    结束语

    总之,Stream 的特性可以归纳为:

    • 不是数据结构
    • 它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。
    • 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。
    • 所有 Stream 的操作必须以 lambda 表达式为参数
    • 不支持索引访问
    • 你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。不过请参阅下一项。
    • 很容易生成数组或者 List
    • 惰性化
    • 很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。
    • Intermediate 操作永远是惰性化的。
    • 并行能力
    • 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
    • 可以是无限的
    • 集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。

    参考

    Java 8 中的 Streams API 详解

    展开全文
  • Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。 有多种方式生成 Stream Source: 从 Collection 和数组 Collection....

     

    Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

    有多种方式生成 Stream Source:

    • 从 Collection 和数组

      • Collection.stream()

      • Collection.parallelStream()

      • Arrays.stream(T array) or Stream.of()

    • 从 BufferedReader

      • java.io.BufferedReader.lines()

    • 静态工厂

      • java.util.stream.IntStream.range()

      • java.nio.file.Files.walk()

    • 自己构建

      • java.util.Spliterator

    • 其它

      • Random.ints()

      • BitSet.stream()

      • Pattern.splitAsStream(java.lang.CharSequence)

      • JarFile.stream()

    流(stream)的操作类型分为两种:

    • Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。

    • Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

    在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

    还有一种操作被称为 short-circuiting。用以指:

    • 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。

    • 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

    流的使用详解

    简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)

    流的构造与转换

    下面提供最常见的几种构造 Stream 的样例。

    // 1. Individual values
    Stream stream = Stream.of("a", "b", "c");
    // 2. Arrays
    String [] strArray = new String[] {"a", "b", "c"};
    stream = Stream.of(strArray);
    stream = Arrays.stream(strArray);
    // 3. Collections
    List<String> list = Arrays.asList(strArray);
    stream = list.stream();

    需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:

    IntStream、LongStream、DoubleStream。当然我们也可以用 Stream<Integer>、Stream<Long> >、Stream<Double>,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。

    数值流的构造

    IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
    IntStream.range(1, 3).forEach(System.out::println);
    IntStream.rangeClosed(1, 3).forEach(System.out::println);

    流转换为其它数据结构

    // 1. Array
    String[] strArray1 = stream.toArray(String[]::new);
    // 2. Collection
    List<String> list1 = stream.collect(Collectors.toList());
    List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
    Set set1 = stream.collect(Collectors.toSet());
    Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
    // 3. String
    String str = stream.collect(Collectors.joining()).toString();

    一个 Stream 只可以使用一次,上面的代码只是示例,为了简洁而重复使用了数次(正常开发只能使用一次)。

    流的操作

    接下来,当把一个数据结构包装成 Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。

    • Intermediate:

    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

    • Terminal:

    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

    • Short-circuiting:

    anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

    典型用法示例

    map/flatMap

    //转换大写,wordList为单词集合List<String>类型
    List<String> output = wordList.stream().map(String::toUpperCase).collect(Collectors.toList());

    求平方数

    //这段代码生成一个整数 list 的平方数 {1, 4, 9, 16}。
    List<Integer> nums = Arrays.asList(1, 2, 3, 4);
    List<Integer> squareNums = nums.stream().map(n -> n * n).collect(Collectors.toList());

    从上面例子可以看出,map 生成的是个 1:1 映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。

    //将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。
    Stream<List<Integer>> inputStream = Stream.of(
     Arrays.asList(1),
     Arrays.asList(2, 3),
     Arrays.asList(4, 5, 6)
     );
    Stream<Integer> outputStream = inputStream.flatMap((childList) -> childList.stream());
    List<Integer> list =outputStream.collect(Collectors.toList());
    System.out.println(list.toString());

    filter

    filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。

    //留下偶数,经过条件“被 2 整除”的 filter,剩下的数字为 {2, 4, 6}。
    Integer[] sixNums = {1, 2, 3, 4, 5, 6};
    Integer[] evens =Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
    //把每行的单词用 flatMap 整理到新的 Stream,然后保留长度不为 0 的,就是整篇文章中的全部单词了。
    //REGEXP为正则表达式,具体逻辑具体分析
    List<String> output = reader.lines().
        flatMap(line -> Stream.of(line.split(REGEXP))).
        filter(word -> word.length() > 0).collect(Collectors.toList());

    forEach

    forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。

    //打印所有男性姓名,roster为person集合类型为List<Pserson>
    // Java 8
    roster.stream().filter(p -> p.getGender() == Person.Sex.MALE).forEach(p -> System.out.println(p.getName()));
    // Pre-Java 8
    for (Person p : roster) {
        if (p.getGender() == Person.Sex.MALE) {
            System.out.println(p.getName());
        }
    }

    当需要为多核系统优化时,可以 parallelStream().forEach(),只是此时原有元素的次序没法保证,并行的情况下将改变串行时操作的行为,此时 forEach 本身的实现不需要调整,而 Java8 以前的 for 循环 code 可能需要加入额外的多线程逻辑。

    另外一点需要注意,forEach 是 terminal 操作,因此它执行后,Stream 的元素就被“消费”掉了,你无法对一个 Stream 进行两次 terminal 运算。下面代码是错误的

    //错误代码示例,一个stream不可以使用两次
    stream.forEach(element -> doOneThing(element));
    stream.forEach(element -> doAnotherThing(element));

    相反,具有相似功能的 intermediate 操作 peek 可以达到上述目的。如下是出现在该 api javadoc 上的一个示例。

    // peek 对每个元素执行操作并返回一个新的 Stream
    Stream.of("one", "two", "three", "four")
         .filter(e -> e.length() > 3)
         .peek(e -> System.out.println("Filtered value: " + e))
         .map(String::toUpperCase)
         .peek(e -> System.out.println("Mapped value: " + e))
         .collect(Collectors.toList());

    findFirst

    这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。

    这里比较重点的是它的返回值类型:Optional。这也是一个模仿 Scala 语言中的概念,作为一个容器,它可能含有某值,或者不包含。使用它的目的是尽可能避免 NullPointerException。

    String strA = " abcd ", strB = null;
    print(strA);
    print("");
    print(strB);
    getLength(strA);
    getLength("");
    getLength(strB);
    //输出text不为null的值
    public static void print(String text) {
        // Java 8
         Optional.ofNullable(text).ifPresent(System.out::println);
        // Pre-Java 8
         if (text != null) {
            System.out.println(text);
         }
     }
    //输出text的长度,避免空指针
    public static int getLength(String text) {
        // Java 8
        return Optional.ofNullable(text).map(String::length).orElse(-1);
        // Pre-Java 8
        //return if (text != null) ? text.length() : -1;
    }

    在更复杂的 if (xx != null) 的情况中,使用 Optional 代码的可读性更好,而且它提供的是编译时检查,能极大的降低 NPE 这种 Runtime Exception 对程序的影响,或者迫使程序员更早的在编码阶段处理空值问题,而不是留到运行时再发现和调试。

    Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average() 返回 OptionalDouble 等等

    reduce

    这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。

    // reduce用例
    // 字符串连接,concat = "ABCD"
    String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
    // 求最小值,minValue = -3.0
    double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 
    // 求和,sumValue = 10, 有起始值
    int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
    // 求和,sumValue = 10, 无起始值,返回Optional,所以有get()方法
    sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
    // 过滤,字符串连接,concat = "ace"
    concat = Stream.of("a", "B", "c", "D", "e", "F")
        .filter(x -> x.compareTo("Z") > 0)
        .reduce("", String::concat);

    limit/skip

    limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素(它是由一个叫 subStream 的方法改名而来)。

    // limit 和 skip 对运行次数的影响
    public void testLimitAndSkip() {
         List<Person> persons = new ArrayList();
         for (int i = 1; i <= 10000; i++) {
             Person person = new Person(i, "name" + i);
             persons.add(person);
        }
        List<String> personList2 = persons.stream()
            .map(Person::getName).limit(10).skip(3)
            .collect(Collectors.toList());
        
        System.out.println(personList2);
    }
    private class Person {
        public int no;
        private String name;
        public Person (int no, String name) {
            this.no = no;
            this.name = name;
        }
         public String getName() {
            System.out.println(name);
            return name;
         }
    }
    //结果
    name1
    name2
    name3
    name4
    name5
    name6
    name7
    name8
    name9
    name10
    [name4, name5, name6, name7, name8, name9, name10]
    //这是一个有 10,000 个元素的 Stream,但在 short-circuiting 操作 limit 和 skip 的作用下,管道中 map 操作指定的 getName() 方法的执行次数为 limit 所限定的 10 次,而最终返回结果在跳过前 3 个元素后只有后面 7 个返回。

    有一种情况是 limit/skip 无法达到 short-circuiting 目的的,就是把它们放在 Stream 的排序操作后,原因跟 sorted 这个 intermediate 操作有关:此时系统并不知道 Stream 排序后的次序如何,所以 sorted 中的操作看上去就像完全没有被 limit 或者 skip 一样。

     List<Person> persons = new ArrayList();
     for (int i = 1; i <= 5; i++) {
         Person person = new Person(i, "name" + i);
         persons.add(person);
     }
    List<Person> personList2 = persons.stream().sorted((p1, p2) -> 
    p1.getName().compareTo(p2.getName())).limit(2).collect(Collectors.toList());
    System.out.println(personList2);
    //结果
    name2
    name1
    name3
    name2
    name4
    name3
    name5
    name4
    [stream.StreamDW$Person@816f27d, stream.StreamDW$Person@87aac27]
    //虽然最后的返回元素数量是 2,但整个管道中的 sorted 表达式执行次数没有像前面例子相应减少。

    sorted

    对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。

    List<Person> persons = new ArrayList();
     for (int i = 1; i <= 5; i++) {
         Person person = new Person(i, "name" + i);
         persons.add(person);
     }
    List<Person> personList2 = persons.stream().limit(2).sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
    System.out.println(personList2);
    //结果
    name2
    name1
    [stream.StreamDW$Person@6ce253f1, stream.StreamDW$Person@53d8d10a]

    min/max/distinct

    min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现,但前者的性能会更好,为 O(n),而 sorted 的成本是 O(n log n)。同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作。

    //找出最长一行的长度
    BufferedReader br = new BufferedReader(new FileReader("c:\\Service.log"));
    int longest = br.lines().mapToInt(String::length).max().getAsInt();
    br.close();
    System.out.println(longest);
    //找出全文的单词,转小写,并排序,使用 distinct 来找出不重复的单词。单词间只有空格
    List<String> words = br.lines()
        .flatMap(line -> Stream.of(line.split(" ")))
        .filter(word -> word.length() > 0)
        .map(String::toLowerCase)
        .distinct().sorted()
        .collect(Collectors.toList());
    br.close();
    System.out.println(words);
    ​

    Match

    Stream 有三个 match 方法,从语义上说:

    • allMatch:Stream 中全部元素符合传入的 predicate,返回 true

    • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true

    • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true

    它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。

    //Match使用示例
    List<Person> persons = new ArrayList();
    persons.add(new Person(1, "name" + 1, 10));
    persons.add(new Person(2, "name" + 2, 21));
    persons.add(new Person(3, "name" + 3, 34));
    persons.add(new Person(4, "name" + 4, 6));
    persons.add(new Person(5, "name" + 5, 55));
    boolean isAllAdult = persons.stream().allMatch(p -> p.getAge() > 18);
    System.out.println("All are adult? " + isAllAdult);
    boolean isThereAnyChild = persons.stream().anyMatch(p -> p.getAge() < 12);
    System.out.println("Any child? " + isThereAnyChild);
    //结果
    All are adult? false
    Any child? true

    自己生成流

    通过实现 Supplier 接口,你可以自己来控制流的生成。这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。由于它是无限的,在管道中,必须利用 limit 之类的操作限制 Stream 大小。

    //生成10个随机数
    Random seed = new Random();
    Supplier<Integer> random = seed::nextInt;
    Stream.generate(random).limit(10).forEach(System.out::println);
    //Another way
    IntStream.generate(() -> (int) (System.nanoTime() % 100)).
    limit(10).forEach(System.out::println);

    Stream.generate() 还接受自己实现的 Supplier。例如在构造海量测试数据的时候,用某种自动的规则给每一个变量赋值;或者依据公式计算 Stream 的每个元素值。这些都是维持状态信息的情形。

    Stream.generate(new PersonSupplier()).
        limit(10).forEach(p -> System.out.println(p.getName() + ", " + p.getAge()));
    private class PersonSupplier implements Supplier<Person> {
         private int index = 0;
         private Random random = new Random();
         @Override
         public Person get() {
            return new Person(index++, "StormTestUser" + index, random.nextInt(100));
         }
    }
    //结果
    StormTestUser1, 9
    StormTestUser2, 12
    StormTestUser3, 88
    StormTestUser4, 51
    StormTestUser5, 22
    StormTestUser6, 28
    StormTestUser7, 81
    StormTestUser8, 51
    StormTestUser9, 4
    StormTestUser10, 76

    Stream.iterate

    iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(例如 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推

    //生成等差数列
    Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));
    //结果
    0 3 6 9 12 15 18 21 24 27

    Stream.generate 相仿,在 iterate 时候管道必须有 limit 这样的操作来限制 Stream 大小。

    用 Collectors 来进行 reduction 操作

    java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。

    groupingBy/partitioningBy

    //按照年龄归组
    Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier())
        .limit(100).collect(Collectors.groupingBy(Person::getAge));
    Iterator it = personGroups.entrySet().iterator();
    while (it.hasNext()) {
         Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
         System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
    }
    //上面的 code,首先生成 100 人的信息,然后按照年龄归组,相同年龄的人放到同一个 list 中,如下的输出:
    Age 0 = 2
    Age 1 = 2
    Age 5 = 2
    Age 8 = 1
    Age 9 = 1
    Age 11 = 2
    ……
    Map<Boolean, List<Person>> children = Stream.generate(new PersonSupplier())
        .limit(100).collect(Collectors.partitioningBy(p -> p.getAge() < 18));
    System.out.println("Children number: " + children.get(true).size());
    System.out.println("Adult number: " + children.get(false).size());
    //结果
    Children number: 23 
    Adult number: 77

    Stream 的特性可以归纳为:

    • 不是数据结构

    • 它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。

    • 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。

    • 所有 Stream 的操作必须以 lambda 表达式为参数

    • 不支持索引访问

    • 你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。不过请参阅下一项。

    • 很容易生成数组或者 List

    • 惰性化

    • 很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。

    • Intermediate 操作永远是惰性化的。

    • 并行能力

    • 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。

    • 可以是无限的

      • 集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。

    展开全文
  • Stream语法详解

    2016-05-16 11:32:23
    1. Stream初体验 我们先来看看Java里面是怎么定义Stream的: A sequence of elements supporting sequential and parallel aggregate operations. 我们来解读一下上面的那句话: Stream是元素的集合...

    1. Stream初体验

    我们先来看看Java里面是怎么定义Stream的:

    A sequence of elements supporting sequential and parallel aggregate operations.

    我们来解读一下上面的那句话:

    1. Stream是元素的集合,这点让Stream看起来用些类似Iterator;
    2. 可以支持顺序和并行的对原Stream进行汇聚的操作;

    大家可以把Stream当成一个高级版本的Iterator。原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了!(这个秘籍,一般人我不告诉他:))大家看完这些可能对Stream还没有一个直观的认识,莫急,咱们来段代码。

    1 //Lists是Guava中的一个工具类
    2 List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6);
    3 nums.stream().filter(num -> num != null).count();

    上面这段代码是获取一个List中,元素不为null的个数。这段代码虽然很简短,但是却是一个很好的入门级别的例子来体现如何使用Stream,正所谓“麻雀虽小五脏俱全”。我们现在开始深入解刨这个例子,完成以后你可能可以基本掌握Stream的用法!

    1.1 剖析Stream通用语法

    图片就是对于Stream例子的一个解析,可以很清楚的看见:原本一条语句被三种颜色的框分割成了三个部分。红色框中的语句是一个Stream的生命开始的地方,负责创建一个Stream实例;绿色框中的语句是赋予Stream灵魂的地方,把一个Stream转换成另外一个Stream,红框的语句生成的是一个包含所有nums变量的Stream,进过绿框的filter方法以后,重新生成了一个过滤掉原nums列表所有null以后的Stream;蓝色框中的语句是丰收的地方,把Stream的里面包含的内容按照某种算法来汇聚成一个值,例子中是获取Stream中包含的元素个数。如果这样解析以后,还不理解,那就只能动用“核武器”–图形化,一图抵千言!

    在此我们总结一下使用Stream的基本步骤:

    1. 创建Stream;
    2. 转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(**可以有多次转换**);
    3. 对Stream进行聚合(Reduce)操作,获取想要的结果;

    2. 创建Stream

    最常用的创建Stream有两种途径:

    1. 通过Stream接口的静态工厂方法(注意:Java8里接口可以带静态方法);
    2. 通过Collection接口的默认方法(默认方法:Default method,也是Java8中的一个新特性,就是接口中的一个带有实现的方法,后续文章会有介绍)–stream(),把一个Collection对象转换成Stream

    2.1 使用Stream静态方法来创建Stream

    1. of方法:有两个overload方法,一个接受变长参数,一个接口单一值

    1 Stream<Integer> integerStream = Stream.of(1235);
    2 Stream<String> stringStream = Stream.of("taobao");

    2. generator方法:生成一个无限长度的Stream,其元素的生成是通过给定的Supplier(这个接口可以看成一个对象的工厂,每次调用返回一个给定类型的对象)

    1 Stream.generate(new Supplier<Double>() {
    2     @Override
    3     public Double get() {
    4         return Math.random();
    5     }
    6 });
    7 Stream.generate(() -> Math.random());
    8 Stream.generate(Math::random);

    三条语句的作用都是一样的,只是使用了lambda表达式和方法引用的语法来简化代码。每条语句其实都是生成一个无限长度的Stream,其中值是随机的。这个无限长度Stream是懒加载,一般这种无限长度的Stream都会配合Stream的limit()方法来用。
    3. iterate方法:也是生成无限长度的Stream,和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环

    1 Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);

    这段代码就是先获取一个无限长度的正整数集合的Stream,然后取出前10个打印。千万记住使用limit方法,不然会无限打印下去。

    2.2 通过Collection子类获取Stream

    这个在本文的第一个例子中就展示了从List对象获取其对应的Stream对象,如果查看Java doc就可以发现Collection接口有一个stream方法,所以其所有子类都都可以获取对应的Stream对象。

    1 public interface Collection<E> extends Iterable<E> {
    2     //其他方法省略
    3     default Stream<E> stream() {
    4         return StreamSupport.stream(spliterator(), false);
    5     }
    6 }

    3. 转换Stream

    转换Stream其实就是把一个Stream通过某些行为转换成一个新的Stream。Stream接口中定义了几个常用的转换方法,下面我们挑选几个常用的转换方法来解释。
    1. distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;

    distinct方法示意图(**以下所有的示意图都要感谢[RxJava](https://github.com/Netflix/RxJava)项目的doc中的图片给予的灵感, 如果示意图表达的有错误和不准确的地方,请直接联系我。**):

    2. filter: 对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素;

    filter方法示意图:

    3. map: 对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗;

    map方法示意图:

    4. flatMap:和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中;

    flatMap方法示意图:

    5. peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;

    peek方法示意图:

    6. limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;

    limit方法示意图:

    7. skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;

    skip方法示意图:

    8. 在一起,在一起!

    1 List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
    2 System.out.println(“sum is:”+nums.stream().filter(num -> num != null).
    3             distinct().mapToInt(num -> num * 2).
    4             peek(System.out::println).skip(2).limit(4).sum());

    这段代码演示了上面介绍的所有转换方法(除了flatMap),简单解释一下这段代码的含义:给定一个Integer类型的List,获取其对应的Stream对象,然后进行过滤掉null,再去重,再每个元素乘以2,再每个元素被消费的时候打印自身,在跳过前两个元素,最后去前四个元素进行加和运算(解释一大堆,很像废话,因为基本看了方法名就知道要做什么了。这个就是声明式编程的一大好处!)。大家可以参考上面对于每个方法的解释,看看最终的输出是什么。
    9. 性能问题
    有些细心的同学可能会有这样的疑问:在对于一个Stream进行多次转换操作,每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是一个for循环里把所有操作都做掉的N(转换的次数)倍啊。其实不是这样的,转换操作都是lazy的,多个转换操作只会在汇聚操作(见下节)的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。

    4. 汇聚(Reduce)Stream

    汇聚这个词,是我自己翻译的,如果大家有更好的翻译,可以在下面留言。在官方文档中是reduce,也叫fold。

    在介绍汇聚操作之前,我们先看一下Java doc中对于其定义:

    A reduction operation (also called a fold) takes a sequence of input elements and combines them into a single summary result by repeated application of a combining operation, such as finding the sum or maximum of a set of numbers, or accumulating elements into a list. The streams classes have multiple forms of general reduction operations, called reduce() and collect(), as well as multiple specialized reduction forms such as sum(), max(), or count().

    简单翻译一下:汇聚操作(也称为折叠)接受一个元素序列为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。比如查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List对象。Stream接口有一些通用的汇聚操作,比如reduce()和collect();也有一些特定用途的汇聚操作,比如sum(),max()和count()。注意:sum方法不是所有的Stream对象都有的,只有IntStream、LongStream和DoubleStream是实例才有。

    下面会分两部分来介绍汇聚操作:

    1. 可变汇聚:把输入的元素们累积到一个可变的容器中,比如Collection或者StringBuilder;
    2. 其他汇聚:除去可变汇聚剩下的,一般都不是通过反复修改某个可变对象,而是通过把前一次的汇聚结果当成下一次的入参,反复如此。比如reduce,count,allMatch;

    4.1 可变汇聚

    可变汇聚对应的只有一个方法:collect,正如其名字显示的,它可以把Stream中的要有元素收集到一个结果容器中(比如Collection)。先看一下最通用的collect方法的定义(还有其他override方法):

    1 <R> R collect(Supplier<R> supplier,
    2                   BiConsumer<R, ? super T> accumulator,
    3                   BiConsumer<R, R> combiner);

    先来看看这三个参数的含义:Supplier supplier是一个工厂函数,用来生成一个新的容器;BiConsumer accumulator也是一个函数,用来把Stream中的元素添加到结果容器中;BiConsumer combiner还是一个函数,用来把中间状态的多个结果容器合并成为一个(并发的时候会用到)。看晕了?来段代码!

    1 List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
    2     List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
    3             collect(() -> new ArrayList<Integer>(),
    4                     (list, item) -> list.add(item),
    5                     (list1, list2) -> list1.addAll(list2));

    上面这段代码就是对一个元素是Integer类型的List,先过滤掉全部的null,然后把剩下的元素收集到一个新的List中。进一步看一下collect方法的三个参数,都是lambda形式的函数(*上面的代码可以使用方法引用来简化,留给读者自己去思考*)。

    • 第一个函数生成一个新的ArrayList实例;
    • 第二个函数接受两个参数,第一个是前面生成的ArrayList对象,二个是stream中包含的元素,函数体就是把stream中的元素加入ArrayList对象中。第二个函数被反复调用直到原stream的元素被消费完毕;
    • 第三个函数也是接受两个参数,这两个都是ArrayList类型的,函数体就是把第二个ArrayList全部加入到第一个中;

    但是上面的collect方法调用也有点太复杂了,没关系!我们来看一下collect方法另外一个override的版本,其依赖[Collector](http://docs.oracle.com/javase/8/docs/api/java/util/stream/Collector.html)。

    1 <R, A> R collect(Collector<? super T, A, R> collector);

    这样清爽多了!少年,还有好消息,Java8还给我们提供了Collector的工具类–[Collectors](http://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html),其中已经定义了一些静态工厂方法,比如:Collectors.toCollection()收集到Collection中, Collectors.toList()收集到List中和Collectors.toSet()收集到Set中。这样的静态方法还有很多,这里就不一一介绍了,大家可以直接去看JavaDoc。下面看看使用Collectors对于代码的简化:

    1 List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
    2                 collect(Collectors.toList());

    4.2 其他汇聚

    – reduce方法:reduce方法非常的通用,后面介绍的count,sum等都可以使用其实现。reduce方法有三个override的方法,本文介绍两个最常用的,最后一个留给读者自己学习。先来看reduce方法的第一种形式,其方法定义如下:

    1 Optional<T> reduce(BinaryOperator<T> accumulator);

    接受一个BinaryOperator类型的参数,在使用的时候我们可以用lambda表达式来。

    1 List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
    2 System.out.println("ints sum is:" + ints.stream().reduce((sum, item) -&gt; sum + item).get());

    可以看到reduce方法接受一个函数,这个函数有两个参数,第一个参数是上次函数执行的返回值(也称为中间结果),第二个参数是stream中的元素,这个函数把这两个值相加,得到的和会被赋值给下次执行这个函数的第一个参数。要注意的是:**第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素**。这个方法返回值类型是Optional,这是Java8防止出现NPE的一种可行方法,后面的文章会详细介绍,这里就简单的认为是一个容器,其中可能会包含0个或者1个对象。
    这个过程可视化的结果如图:

    reduce方法还有一个很常用的变种:

    1 T reduce(T identity, BinaryOperator<T> accumulator);

    这个定义上上面已经介绍过的基本一致,不同的是:它允许用户提供一个循环计算的初始值,如果Stream为空,就直接返回该值。而且这个方法不会返回Optional,因为其不会出现null值。下面直接给出例子,就不再做说明了。

    1 List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
    2 System.out.println("ints sum is:" + ints.stream().reduce(0, (sum, item) -> sum + item));

    – count方法:获取Stream中元素的个数。比较简单,这里就直接给出例子,不做解释了。

    1 List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
    2 System.out.println("ints sum is:" + ints.stream().count());

    – 搜索相关
    – allMatch:是不是Stream中的所有元素都满足给定的匹配条件
    – anyMatch:Stream中是否存在任何一个元素满足匹配条件
    – findFirst: 返回Stream中的第一个元素,如果Stream为空,返回空Optional
    – noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件
    – max和min:使用给定的比较器(Operator),返回Stream中的最大|最小值
    下面给出allMatch和max的例子,剩下的方法读者当成练习。

    1 List<Integer&gt; ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
    2 System.out.println(ints.stream().allMatch(item -> item < 100));

    3 ints.stream().max((o1, o2) -&gt; o1.compareTo(o2)).ifPresent(System.out::println);



    转自:http://ifeve.com/stream/

    展开全文
  • Java8新特性:Stream流详解 本文章 转载自头条网, 只是觉得好用很详细,所以自己收集 做下笔记,不做任何商业用途,不收任何费用,不喜勿喷。 本文是转载,希望不要涉及到文章版权,只是自己做笔记。_________ 这...
  • Java 8 Stream详解

    2018-05-17 10:39:32
    开始之前 作为 Java API 的新成员,Stream API “允许以声明式的方式处理数据集合”。回顾 “内容介绍” 部分,我们阅读了以下的代码: 代码 class Good { String name; // 商品名称 long price; // 价...
  •  vegetarian = menu.stream().filter(Dish::isVegetarian).collect(Collectors.toList());过滤出偶数,并且不重复的元素。List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);numbers....
  • Java 8 stream的详细用法

    2019-01-08 23:12:43
    Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以...
  • 概述继Java 8系列之Lambda表达式之后,我们来了解StreamStream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便...
  • 主要用到的方法1.Directory.GetParent(pathName).ToString() + “\” + pathName;//getparent()方法 通过文件名获取绝对路径(不含文件名)2.FileStream fs = new FileStream(fullPath, FileMode.OpenOrCreate);...
  • 很多时候需要网络抓包分析,在iPhone上抓包稍有不同,下面介绍三种常用的方式。分析工具以wireshark为例。一、最简单的方式:用PC作为热点,在PC上抓包 优点:简单; 缺点:不能抓真机2G/3G/4G网络数据。...
  • 之前的Java集合中removeIf的使用一文写了使用removeIf来实现按条件对集合进行过滤。这篇文章使用同样是JDK1.8新加入的Stream中filter方法来实现同样的效果。
  • list去重,根据对象某个属性、某几个属性... List unique = list.stream().distinct().collect(Collectors.toList()); 去除List中重复的对象 // Person 对象 public class Person { private String id; ...
  • Java8 Stream常用方法

    2020-05-08 10:41:25
    一、流的初始化与转换: Java中的Stream的所有操作都是针对流的,所以,使用Stream必须要得到Stream对象: 1、初始化一个流: Stream stream = Stream.of("a", "b", "c"); 2、数组转换为一个流: ...
  • 两个星期以前,就有读者强烈要求我写一篇 Java Stream 流的文章,我说市面上不是已经有很多了吗,结果你猜他怎么说:“就想看你写的啊!”你看你看,多么苍白的喜欢啊。那就“勉为其难”写一篇吧,嘻嘻。 单从...
  • java8中的Stream相信大家都使用过,代码可以变得简洁漂亮,甚至习惯后,简直爱不释手,根本不想再使用以前的通俗写法。但是初学者很容易犯一些错误。 本文介绍一下Stream中可能会使用的一个错误用法:Stream.max...
  • Java8新了stream API,需要注意的是Stream和I/O中的流是没有关系的,这个stream主要是要来处理集合数据的,可以将其看作一个高级迭代器。在Collection接口中新增了非抽象的stream方法来获取集合的流。 另外,Java8...
  • 之前也写过很多篇关于Java8使用的文章了,但是回顾一下,好像还没介绍过Java8 Stream的flatMap操作,昨天刚好在工作中遇到一个场景,发现flatMap简直太方便了,这里总结一下flatMap的常规使用。附带讲一下,使用Java...
  • Java 8 Stream.distinct() 示例 在这篇文章里,我们将提供Java8 Stream distinct()示例。 distinct()返回由该流的不同元素组成的流。distinct()是Stream接口的方法。distinct()使用hashCode()和equals...
1 2 3 4 5 ... 20
收藏数 669,911
精华内容 267,964
关键字:

stream