Java8 - 使用Stream API

Java8 - 使用Stream APIjava

Stream 是什么?

Stream represents a sequence of objects from a source, which supports aggregate operations(多种操做). Following are the characteristics(特征) of a Stream −api

  • Sequence of elements − A stream provides a set of elements of specific type in a sequential manner. A stream gets/computes elements on demand. It never stores the elements.数组

  • Source − Stream takes Collections, Arrays, or I/O resources as input source.ide

  • Aggregate operations − Stream supports aggregate operations(多种操做) like filter, map, limit, reduce, find, match, and so on.函数

  • Pipelining − Most of the stream operations return stream itself so that their result can be pipelined. These operations are called intermediate operations and their function is to take input, process them, and return output to the target. collect() method is a terminal operation(终端操做) which is normally present at the end of the pipelining operation to mark the end of the stream.ui

  • Automatic iterations − Stream operations do the iterations internally over the source elements provided, in contrast to Collections where explicit iteration is required.this

 

流是如何建立的?

流有多种建立方式,好比最多见的经过集合,数组spa

经过集合构建流

package com.common.stream;

import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class CreateStreamTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {
        //获取一个stream 流
        Stream<Dish> stream = menu.stream();
        stream.peek(x -> System.out.println("from stream: " + x)).
                filter((x) -> x.getCalories() > 500).
                peek(x -> System.out.println("after filter: " + x)).
                forEach(System.out::println);

        //获取一个并行的流
        Stream parallelStream = menu.parallelStream();

    }
}

 

上面这段代码输出打印,code

from stream: Dish{name='pork', calories=800}
after filter: Dish{name='pork', calories=800}
Dish{name='pork', calories=800}
from stream: Dish{name='beef', calories=700}
after filter: Dish{name='beef', calories=700}
Dish{name='beef', calories=700}
from stream: Dish{name='chicken', calories=400}
from stream: Dish{name='french fries', calories=530}
after filter: Dish{name='french fries', calories=530}
Dish{name='french fries', calories=530}
from stream: Dish{name='rice', calories=350}
from stream: Dish{name='season fruit', calories=120}
from stream: Dish{name='pizza', calories=550}
after filter: Dish{name='pizza', calories=550}
Dish{name='pizza', calories=550}
from stream: Dish{name='prawns', calories=300}
from stream: Dish{name='salmon', calories=450}

从输出来看,Stream 流至关于一个迭代器,依次遍历全部的元素。orm

经过数组构建流

可使用静态方法Arrays.stream从数组建立一个流。它接受一个数组做为参数。

String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();

经过Stream.of(传递显示值)建立流

可使用静态方法Stream.of,经过显式值建立一个流。它能够接受任意数量的参数。例 如,如下代码直接使用Stream.of建立了一个字符串流。而后,能够将字符串转换为大写,再 一个个打印出来:

Stream.of("hello", "world").map(String::toUpperCase).forEach(System.out::println);

经过文件建立流 

Java中用于处理文件等I/O操做的NIO API(非阻塞 I/O)已更新,以便利用Stream API。 java.nio.file.Files中的不少静态方法都会返回一个流。例如,一个颇有用的方法是 Files.lines,它会返回一个由指定文件中的各行构成的字符串流。

long uniqueWords = 0;
try (Stream<String> lines = Files.lines(Paths.get("/Users/flyme/workspace/showcase/common/src/main/java/data.txt"), Charset.defaultCharset())) {
    uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))).distinct()
            .count();

    System.out.println(uniqueWords);
} catch (IOException e) {
    e.printStackTrace();
}

由函数生成流:建立无限流

Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。 这两个操做能够建立所谓的无限流:不像从定集合建立的流那样有定大小的流。由iterate 2 和generate产生的流会用给定的函数按需建立值,所以能够无无尽地计算下去!通常来讲, 应该使用limit(n)来对这种流加以限制,以免打印无穷多个值。

package com.common.stream;

import java.util.function.Supplier;
import java.util.stream.Stream;

public class StreamGenerateTest {

    static class NaturalSupplier implements Supplier<Long> {

        long value = 0;

        @Override
        public Long get() {
            this.value = this.value + 1;
            return this.value;
        }
    }

    public static void main(String[] args) {


//        iterate方法接受一个初始值(在这里是0),还有一个依次应用在每一个产生的新值上的 Lambda(UnaryOperator<t>类型)。
//        这里,咱们使用Lambda n -> n + 2,返回的是前一个元 素加上2。
//        所以,iterate方法生成了一个全部正偶数的流:流的第一个元素是初始值0。
//        而后加 上2来生成新的值2,再加上2来获得新的值4,以此类推。
//        这种iterate操做基本上是顺序的, 由于结果取决于前一次应用。
//        请注意,此操做将生成一个无限流——这个流没有结,由于值是 按需计算的,能够远计算下去。
//        咱们说这个流是无界的。正如咱们前面所讨论的,这是流和集 合之间的一个关键区别。咱们使用limit方法来显式限制流的大小。
//        这里只选择了前10个偶数。 8 而后能够调用forEach终端操做来消费流,并分别打印每一个元素。
        Stream.iterate(0, n -> n + 2)
                .limit(10)
                .forEach(System.out::println);


        //建立一个无穷数列的stream
        Stream<Long> natural = Stream.generate(new NaturalSupplier());
        //使用limit 方法截断流
        natural.peek(n -> {
            System.out.println("from stream:" + n);
        }).map((x) -> x * x).limit(10).forEach(System.out::println);

    }
}

 

Stream 和集合的不一样

一是集合类持有的全部元素都是存储在内存中的,很是巨大的集合类会占用大量的内存,而Stream的元素倒是在访问的时候才被计算出来,这种“延迟计算”的特性有点相似Clojure的lazy-seq,占用内存不多。

二是集合能够遍历屡次,而流只能遍历一次,遍历完以后,咱们就说这个流已经被消费掉了。 你能够从原始数据源那里再得到一个新的流来从新遍历一遍,就像迭代器同样(这里假设它是集 合之类的可重复的源,若是是I/O通道就没戏了)。

三是集合类的迭代逻辑是调用者负责,一般是for循环,这称为外部迭代;而Stream的迭代是隐含在对Stream的各类操做中,例如map(),称为内部迭代。

示例一,经过Stream建立一个无穷大小的天然数集合,使用 Supplier 能够建立一个无穷大小的 Stream。若是用集合是作不到的。

package com.common.stream;

import java.util.function.Supplier;
import java.util.stream.Stream;

public class CreateStreamTest {

    static class NaturalSupplier implements Supplier<Long> {

        long value = 0;

        @Override
        public Long get() {
            this.value = this.value + 1;
            return this.value;
        }
    }

    public static void main(String[] args) {

        //建立一个无穷数列的stream
        Stream<Long> natural = Stream.generate(new NaturalSupplier());
        //使用limit 方法截断流
        natural.peek(n -> {
            System.out.println("from stream:" + n);
        }).map((x) -> x * x).limit(10).forEach(System.out::println);

    }
}

 

示例二,Stream 只能遍历一次,若是遍历屡次,会报错

package com.common.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamIterationTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {
        Stream<Dish> stream = menu.stream();
        stream.peek(x -> System.out.println("from stream: " + x)).
                filter((x) -> x.getCalories() > 500).
                peek(x -> System.out.println("after filter: " + x)).
                forEach(System.out::println);

        stream.forEach(System.out::println);
    }
}

输出,

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
    at com.common.stream.StreamIterationTest.main(StreamIterationTest.java:27)

 

示例三,集合和Stream遍历的不一样

package com.common.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamIterationTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        // for each 循环外部迭代
        for (Dish dish : menu) {
            names.add(dish.getName());
        }

        // 外部迭代方式
        names = new ArrayList<>();
        Iterator<Dish> iterator = menu.iterator();
        while (iterator.hasNext()) {
            Dish d = iterator.next();
            names.add(d.getName());
        }

        names.clear();

        // 使用stream 的内部迭代
        Stream<Dish> stream = menu.stream();
        stream.peek(x -> System.out.println("from stream: " + x)).
                map((x) -> x.getName()).collect(Collectors.toList());

    }
}

参考:https://www.tutorialspoint.com/java8/java8_streams.htm

http://www.infoq.com/cn/articles/java8-new-features-new-stream-api

 

Stream 操做

stream的操做分为为两类,中间操做(intermediate operations)和结束操做(terminal operations),两者特色是:

  1. 中间操做老是会惰式执行,调用中间操做只会生成一个标记了该操做的新stream,仅此而已。
  2. 结束操做会触发实际计算,计算发生时会把全部中间操做积攒的操做以pipeline的方式执行,这样能够减小迭代次数。计算完成以后stream就会失效。

筛选和切片操做

看示例代码,

package com.common.stream;


import java.util.Arrays;
import java.util.List;

public class StreamOperateTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {

        // filter 过滤操做
        menu.stream().filter(n ->
                n.getCalories() > 500
        ).forEach(System.out::println);

        // distinct 去重操做
        Arrays.asList(1, 2, 1, 3, 3, 2, 4).stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);

        // limit 截断流的操做 只保留前两个元素
        menu.stream().filter(n ->
                n.getCalories() > 200
        ).limit(2).forEach(System.out::println);

        // skip 跳过操做 跳过前两个元素,返回剩下的元素
        menu.stream().filter(n ->
                n.getCalories() > 200
        ).skip(2).forEach(System.out::println);
        
    }
}

映射操做

好比map和flatMap方法,能够把一个元素映射成另外一个元素。如下示例对比了 map 和 flatMap的区别,

package com.common.stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

public class StreamMapOperateTest {

    public static void main(String[] args) {

        String[] arrayOfWords = {"Hello", "World", "Bye"};

        List<Stream<String>> res = Arrays.stream(arrayOfWords)
                // 把每一个单词映射成一个数组,好比 "Hello" -> ['H','e','l','l','o']
                .map(word -> word.split(""))
                // 把每一个数组映射成一个Stream<String>, 好比 ['H','e','l','l','o'] -> Stream<String>
                // 效果是 [['H','e','l','l','o'],['W','o','r','l','d'],['B','y','e']] -> [Stream<String>,Stream<String>,Stream<String>]
                .map(Arrays::stream)
                .distinct()
                .collect(toList());

        res.forEach(System.out::println);


        /**
         * 要理解什么是扁平化流,请考虑像[ [1,2,3],[4,5,6],[7,8,9] ]这样的结构,它具备“两个层次”。
         * 扁平化意味着将其转变为“一级”结构: [ 1,2,3,4,5,6,7,8,9 ] 。
         */
        List<String> resStr = Arrays.stream(arrayOfWords)
                .map(word -> word.split(""))
                // 效果是二维数组 到 一维数组
                // [['H','e','l','l','o'],['W','o','r','l','d'],['B','y','e']]  -> ['H','e','l','l','o','W','o','r','l','d','B','y','e']
                .flatMap(Arrays::stream)
                .peek(n -> {
                    System.out.println("after flatMap : " + n);
                })
                .distinct()
                .collect(toList());

        resStr.forEach(System.out::println);


    }
}

查找和匹配

package com.common.stream;

import java.util.Arrays;
import java.util.List;

public class StreamFindTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {

        // anyMatch 操做
        if (menu.stream().anyMatch(x -> x.getCalories() > 500)) {
            System.out.println("anyMatch....");
        }

        // allMatch
        if (menu.stream().allMatch(x -> x.getCalories() < 1000)) {
            System.out.println("allMatch....");
        }

        // noneMatch
        if (menu.stream().noneMatch(x -> x.getCalories() >= 1000)) {
            System.out.println("noneMatch....");
        }

        // findAny 方法返回 Optional 对象
        // findAny方法将返回当前流中的任意元素。
        menu.stream().filter(x -> x.getCalories() > 500).findAny().ifPresent(System.out::println);

        // 找到第一个元素
        menu.stream().filter(x -> x.getCalories() > 500).findFirst().ifPresent(System.out::println);
    }
}

归约操做

到目前为止,终端操做都是返回一个boolean(allMatch之类的)、void (forEach)或Optional对象(findAny等),或者使用collect来将流中的全部元素组合成一个List。

归约操做则是相似求和,将流归约成一个值。 

以下,求全部菜谱的卡路里值,

package com.common.stream;

import java.util.Arrays;
import java.util.List;

public class StreamReduceTest {

    private static final List<Dish> menu = Arrays.asList(
            new Dish("pork", 800, Dish.Type.MEAT),
            new Dish("beef", 700, Dish.Type.MEAT),
            new Dish("chicken", 400, Dish.Type.MEAT),
            new Dish("french fries", 530, Dish.Type.OTHER),
            new Dish("rice", 350, Dish.Type.OTHER),
            new Dish("season fruit", 120, Dish.Type.OTHER),
            new Dish("pizza", 550, Dish.Type.OTHER),
            new Dish("prawns", 300, Dish.Type.FISH),
            new Dish("salmon", 450, Dish.Type.FISH));

    public static void main(String[] args) {

        // map reduce 操做
        int sum = menu.stream().map(Dish::getCalories).reduce(0, (a, b) -> a + b);

        System.out.println(sum);

    }
}

求最大最小值

// 求最大最小值
Optional<Integer> min = menu.stream().map(Dish::getCalories).reduce(Integer::min);

// 求最大最小值
Optional<Integer> max = menu.stream().map(Dish::getCalories).reduce(Integer::max);

if (min.isPresent()) {
    System.out.println(min.get());
}

if (max.isPresent()) {
    System.out.println(max.get());
}

=======END=======

相关文章
相关标签/搜索