Java 8(三):Stream 流的认识及简单应用

1、什么是流(Stream)

比较文献的定义,Stream是从支持数据处理操作的源生成的元素序列。根据对流(Stream)的定义,我们可以从中提取以下部分:

元素序列: 流为特定元素类型的一组有序值提供接口。 但是,流实际上并不存储元素; 它们是按需计算的。

源: 流来自数据提供源,例如集合,数组或I / O资源。

数据处理操作: Streams支持类似SQL的操作和函数式编程语言的常用操作,例如filter,map,reduce,find,match,sorted等。

此外,流操作有两个基本特征

流水线: 许多流操作本身返回一个流。 这允许操作被链接以形成更大的管道。 这可以实现某些优化,例如懒惰和短路。流水线的操作可以看作对数据源进行数据库式的查询。

内部迭代: 与显式迭代的集合(外部迭代)相比,流操作是在背后进行的。

在这里插入图片描述

2、为什么需要流(Stream)

Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。

Stream API 借助于新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。

通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。

3、Java 流(Stream) 和 集合(Collection)

  • 集合(Collection)是一个内存中的数据结构,它存储包含着所有的值,每一个元素都是存放在内存里的
  • 流(Stream)是概念上的固定的数据结构,元素都是按需计算的。从另一个角度来说,流就是延迟创建的集合,只要在需要的时候才会计算值,得到结果。
  • 集合(Collection)需要显式迭代(Iterator),也就是外部迭代,能显式地一个一个遍历元素并对其执行某些操作。而流(Stream)使用的是内部迭代,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

基于以上几点,我们列出了Stream的各种特性,如下:

  • 不是数据结构
  • 专为lambdas设计
  • 不支持索引访问
  • 可以轻松输出为数组或列表
  • 支持延迟访问
  • 可并行

4、流(Stream)的构成

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

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

5、流(Stream)的使用

5.1、流(Stream)的操作类型

流的操作类型分为两种,中间操作和终端操作:

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

终端操作(Terminal): 一个流只能有一个终端操作,当这个操作执行后,流就无法再被操作。所以这是流的最后一个操作。终端操作执行,才会真正开始流的遍历,并且会生成一个结果。

常见的中间操作:

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

常见的终端操作:

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

5.2、流(Stream)的创建

有许多方法可以创建不同源的流实例。 一旦创建,实例将不会修改其源,因此允许从单个源创建多个实例。

5.2.1、空流(Empty Stream)

如果创建空流,则应使用empty()方法:

Stream<String> streamEmpty = Stream.empty();

通常情况下,在创建时使用empty()方法以避免为没有元素的流返回null:

public Stream<String> streamOf(List<String> list) {
    return list == null || list.isEmpty() ? Stream.empty() : list.stream();
}
5.2.2、通过集合(Collection)创建

Stream 也可以通过任何类型的Collection(Collection,List,Set)创建:

Collection<String> collection = Arrays.asList("a", "b", "c");
Stream<String> streamOfCollection = collection.stream();
5.2.3、通过数组(Array)创建

Array也可以是Stream的源:

Stream<String> streamOfArray = Stream.of("a", "b", "c");

也可以从现有数组或数组的一部分创建:

String[] arr = new String[]{"a", "b", "c"};
Stream<String> streamOfArrayFull = Arrays.stream(arr);
Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);
5.2.4、通过构造器 Stream.builder() 创建

使用构建器时,应在语句的右侧部分另外指定所需类型,否则build()方法将创建Stream 的实例:

Stream<String> streamBuilder =
  Stream.<String>builder().add("a").add("b").add("c").build();
5.2.5、通过 Stream.generate() 创建

generate()方法接受Supplier 以生成元素。 由于生成的流是无限的,开发人员应指定所需的大小,否则generate()方法将一直有效,直到达到内存限制:

Stream<String> streamGenerated =
  Stream.generate(() -> "element").limit(10);
5.2.6、通过 Stream.iterate() 创建

创建无限流的另一种方法是使用iterate()方法:

Stream<Integer> streamIterated = Stream.iterate(40, n -> n + 2).limit(20);

结果流的第一个元素是iterate()方法的第一个参数。 为了创建每个后续元素,指定的函数将应用于前一个元素。 在上面的示例中,第二个元素将是42。

5.2.7、原始流(Stream)

Java 8 对于基本数值型 提供了三种对应的包装类型 Stream:IntStream,LongStream,DoubleStream。当然我们也可以用 Stream、Stream >、Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。

IntStream intStream = IntStream.range(1, 3);
LongStream longStream = LongStream.rangeClosed(1, 3);

range(int startInclusive,int endExclusive)方法创建从第一个参数到第二个参数的有序流。 它增加后续元素的值,步长等于1.结果不包括最后一个参数,它只是序列的上限。

rangeClosed(int startInclusive,int endInclusive)方法, 第二个元素包含在内。 这两种方法可用于生成三种类型的流中的任何一种。

5.2.8、通过 String 创建

String也可以用作创建流的源。

借助String类的chars()方法。 由于JDK中没有接口CharStream,因此IntStream用于表示字符流。

IntStream streamOfChars = "abc".chars();

以下示例根据指定的RegEx将String拆分:

Stream<String> streamOfString =
  Pattern.compile(", ").splitAsStream("a, b, c");
5.2.9、通过 File 创建

Java NIO类Files允许通过lines()方法生成文本文件的Stream 。 文本的每一行都成为流的一个元素:

Path path = Paths.get("C:\\file.txt");
Stream<String> streamOfStrings = Files.lines(path);
Stream<String> streamWithCharset = 
  Files.lines(path, Charset.forName("UTF-8"));

5.3、流(Steam)的使用示例

5.3.1、中间操作
5.3.1.1、Stream.filter()

过滤器根据给定的谓词过滤流的所有元素,并返回一个新的流。

在这里插入图片描述

 //构造数据
    public static List<Apple> appleList = Arrays.asList(new Apple(60, "green"), new Apple(80, "yellow"), new Apple(200, "red"));

    public static void main(String[] args) {
        //filter
        appleList.stream().filter(a->a.getColor().equals("green")).forEach(System.out::println);
    }
5.3.1.2、Stream.map()

对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素

在这里插入图片描述

以下示例将每个苹果重量加5。 你也可以使用map将每个对象转换为另一种类型。

 //构造数据
    public static List<Apple> appleList = Arrays.asList(new Apple(60, "green"), new Apple(80, "yellow"), new Apple(200, "red"));

    public static void main(String[] args) {
        //filter
        //appleList.stream().filter(a->a.getColor().equals("green")).forEach(System.out::println);
        // map
        appleList.stream().map(a->a.getWeight()+5).forEach(System.out::println);
    }
5.3.1.3、Stream.flatMap()

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

在这里插入图片描述

 //构造数据
    public static List<Apple> appleList = Arrays.asList(new Apple(60, "green"), new Apple(80, "yellow"), new Apple(200, "red"));

    public static void main(String[] args) {
//        //filter——过滤
//        appleList.stream().filter(a->a.getColor().equals("green")).forEach(System.out::println);
//        // map——映射(一对一)
//        appleList.stream().map(a -> a.getWeight() + 5).forEach(System.out::println);
//        //sorted——排序
//        appleList.stream().sorted(Comparator.comparing(Apple::getWeight)).forEach(System.out::println);
        //flatMap
        appleList.stream().flatMap(a -> Stream.of("苹果颜色:".concat(a.getColor()))).forEach(System.out::println);
    }
5.3.1.4、Stream.sorted()

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

 //构造数据
    public static List<Apple> appleList = Arrays.asList(new Apple(60, "green"), new Apple(80, "yellow"), new Apple(200, "red"));

    public static void main(String[] args) {
//        //filter——过滤
//        appleList.stream().filter(a->a.getColor().equals("green")).forEach(System.out::println);
//        // map——映射(一对一)
//        appleList.stream().map(a -> a.getWeight() + 5).forEach(System.out::println);
        // sorted——排序
        appleList.stream().sorted(Comparator.comparing(Apple::getWeight)).forEach(System.out::println);
    }
5.3.2、终端操作

终端操作返回某种类型的结果,而不是返回Stream。

5.3.2.1、Stream.forEach()

Stream 提供了新的方法 ‘forEach’ 来迭代流中的每个数据。

//forEach
        appleList.stream().forEach(System.out::println);
5.3.2.2、Stream.collect()

collect()方法用于从steam 收集元素并将它们存储在集合中。

//collect
        appleList.stream().map(a -> a.getColor().equals("green")).collect(Collectors.toList());
5.3.2.3、Stream.match()

Stream 有三个 match 方法:

  • allMatch:Stream 中全部元素符合传入的 predicate,返回 true
  • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
  • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
 //anyMatch
        appleList.stream().anyMatch(a->a.getColor().contains("g"));
        //noneMatch
        appleList.stream().noneMatch(a->a.getColor().contains("g"));
        //allMatch
        appleList.stream().allMatch(a->a.getColor().contains("g"));
5.3.2.4、Stream.count()

返回流中元素的数量为long。

//count
appleList.stream().count();
5.3.2.5、Stream.reduce()

这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 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);

参考资料:

https://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html

Java 8 实战

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值