Java8 - 使用Stream API

Java8 - 使用Stream API

Stream 是什么?

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

  • 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.

  • 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.

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

 

流是如何创建的?

流有多种创建方式,比如最常见的通过集合,数组

通过集合构建流

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();

    }
}

 

上面这段代码输出打印,

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 流相当于一个迭代器,依次遍历所有的元素。

通过数组构建流

可以使用静态方法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=======

转载于:https://my.oschina.net/xinxingegeya/blog/2050467

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值