Java流介绍

你好! 核心Java和Vavr都提供流这是非常方便的工具,并且与上述Optional / Option和Try一起启用了应用程序的功能样式。 像往常一样,我们将从香草Java开始-首先描述什么是流以及如何构建管道。 之后,我们将更深入地研究Vavr Stream,并检查它与Java开箱即用之间的区别。

What is stream in Java?

流是在Java 8中引入的,并在下一版本中进行了更新。 文档将流描述为支持顺序和并行聚合操作的一系列元素。 请不要混淆“ stream”一词:即使在第8版之前,Java还是输入流和输出流,但是这些概念与本文的主人公没有什么共同之处。 爪哇流Java 8中引入的,是单子模式-来自功能语言的概念。 在此,单子代表定义为一系列步骤的计算。

让我们看一下以传统方式编写的简单案例:

List<String> names = Arrays.asList("Anna", "Bob", "Carolina", "Denis", "Anna", "Jack", "Marketa", "Simon", "Anna");

for (String name: names){
    if (name.equalsIgnoreCase("Anna")){
        System.out.println(name);
    }
}

我们在这里所做的是,我们在列表中找到了所有的安娜,然后将它们打印出来。 这是一个简单的操作,但是,尽管如此,仍然需要我们为此类荒谬的任务编写大量代码! 采取另一个代码段:

List<String> names; // same names as before

names.stream().filter(name->name.equalsIgnoreCase("Anna")).forEach(System.out::println);

相同的任务,但现在只需要一串代码。 我们在这里做了什么? 我们建了一个管道:

  1. 查找所有等于安娜打印每个

该管道由一个中间(fliter())并终止(forEach())操作,我们将在本文后面介绍。

Create streams

流是编程的抽象,因此它不等于集合,但是我们从集合创建它。 这些概念通常由以功能性Java开头的开发人员混合使用,但我们需要对其进行区分。 在我们的示例中,我们先创建一个流清单。 有几种初始化流的方法:

From collections

这是最简单,最明显的一种。 Java的采集接口具有内置方法流()返回以该集合为源的顺序Stream。 看看下面的代码片段:

List<Person> people;

Stream<Person> stream = people.stream();

// do something with stream...

Generating streams

如果您没有定义的数据集合,则可以生成流数据。 这对于试验流API方法可能很有用。 我们需要提供一个供应商 that is used to 生成 a random sequence of elements. Method 生成返回无限顺序无序流。 这是一个例子:

DoubleStream numbers = Stream.generate(Math::random);

在这种情况下,我们生成一个流具有随机的Double值。串流和长流还提供一种特殊的方法范围 that we also can utilize to generate a 流. Take a look on a code snippet below:

IntStream integers = IntStream.range(1,20);
integers.forEach(System.out::println);

LongStream longs = LongStream.rangeClosed(1,20);
longs.forEach(System.out::println);

在这两种情况下,我们的范围都在1到20之间,但是输出却不同。 这是由于事实范围和范围Closed return a 范围 that could contains 上限数或不。范围Closed method returns a 范围 that includes 两个限制,而范围从结果中排除第二个值。

ofNullable

用于创建流的另一种静态方法是空值。 它允许我们创建一个包含单个元素或空元素(如果是空值)。NB此方法是Java 9中引入的。

在下面找到代码:

Person anna = null;
Stream<Person> personStream = Stream.ofNullable(anna); 

of

创建流的另一种值得一看的方法是的. There are two overloaded versions 的 this method:

  • (T元素)的(T ...元素)

In the first case, it returns a sequential Stream containing a single element T. In the second one, it returns a sequential ordered stream whose elements are the specified values. NB that second version uses varargs as an argument. This code snippet illustrates this method:

Stream<Car> cars = Stream.of(new Car("tesla"), new Car("skoda"), new Car("toyota"), new Car("mazda"));
cars.forEach(System.out::println);

Stream<Car> skoda = Stream.of(new Car("skoda"));
skoda.forEach(System.out::println);

iterate

如同空值,此方法在Java 9中引入。重复接受两个参数:初始值(种子)和一元运算符 that produces a sequence. The method starts with the 种子 value and iteratively applies the given function to get the next element. Here is an example:

Stream.iterate(0, i -> i + 2);

Empty stream

最后,我们总是可以创建一个空的流。NB我们提到空值方法能够 return an 空的 stream, but there is another approach to get 明确地 空的流。空的 method returns an 空的 sequential Stream:

Stream<Double> empty = Stream.empty();

What about Builder?

我们探索了用于创建流的静态方法。 但是,尽管有这些方法,但还有另一种方法:使用Builder。流生成器通过单独生成元素并将其添加到生成器而无需临时集合或缓冲区,从而允许创建流。 让我们看一下:

// 1. create builder
Stream.Builder<String> builder = Stream.builder;

// 2. create stream
Stream<String> names = builder.add("anna").add("bob")
                        .add("carolina").add("david")
                        .build();

建造者是构建流的另一种方法。 我们初始化一个流生成器实例,然后使用加方法用值填充它。 最后,我们进行转换建造者至流通过建立方法。

Assemble a pipeline

我们对流创建的主题进行了广泛的介绍,并观察了实现流的关键方法。 现在,当我们获得一个流实例时,我们可以设置一个管道为了对流做一些有用的事情。 从技术角度来看,管道由一个源(集合,生成器函数)组成; 其次是零个或多个中间操作和终奌站 operation. The graph below represents a concept of 管道:

在本节中,我们简要探讨中间操作和终端操作的作用,并观察其中最著名的操作。

Intermediate operations

中间操作返回新流,并且懒。 它们的惰性意味着对源数据进行了实际计算只要 after the terminal operation is invoked, and source elements are consumed 只要 as needed. We can chain multiple intermediate operations, as each returns a new 流宾语。 看看下面的图:

现在让我们快速浏览一下最常用的中间操作。

Filter

在文章开头,我们已经使用此操作来过滤集合并找到匹配的名称。 简而言之,它将返回一个新流,其中包含与给定条件匹配的元素。 此方法接受谓语指定条件。

names.stream().filter(name->name.equalsIgnoreCase("Joe"));

在此代码中,我们使用过滤仅查找匹配的名称乔。 结果,我们将获得新 stream with only 乔s.

Map

有几种映射操作,我决定将它们组合在一个标题下。 让我们从一般开始地图方法。 它返回一个新流,其中包含应用地图per对流的元素起作用。 这是一个示例代码:

Stream.of("anna", "benjamin", "carol", "david", "eliska", "frank")
    .map(String::toUpperCase)
    .forEach(System.out::println);

There we also have a source data that is a list of names. We apply mapping function to transform names into UPPERCASE STRINGS. In all cases, mapper is a Function that accepts one argument and produces a result. There other, specific mapping operations:

  • mapToInt=产生一个由应用给定的mapper函数的结果组成的IntStreammapToDouble=产生一个DoubleStream,其中包含应用给定的mapper函数的结果mapToLong=产生一个LongStream,其中包括应用给定的mapper函数的结果
Distinct

Java Stream API中另一个值得注意的中间操作是不同。 它产生了不同数据中的(唯一)元素。 从技术角度来看,不同方法适用于等于 of enitites in order to avoid duplicates. For ordered streams, the selection of 不同 elements is stable, while for unordered ones, Java provides no stability guarantees.

List<Integer> numbers = Arrays.asList(1, 1, 2, 3, 3, 4, 5, 5); 
numbers.stream().distinct().forEach(System.out::println); 

That is how this method works with numbers. In your custom entites, as it was mentioned you have to override equals and hashCode in order to distinct unique elements. I advice you to go read about overriding hashCode and equals before you will do this.

Sort

排序是我们必须对流执行的另一项重要任务。已排序 method is an intermediate operation that provides a stream consisting of the elements of this stream, 已排序 according to 自然秩序。 看看下面的代码片段:

List<Integer> numbers = Arrays.asList(-9, -18, 0, 25, 4); 
numbers.stream().sorted().forEach(System.out::println); 

Again, this is how sorting works with numbers. With custom entites you need to implement Comparable, otherwise, ClassCastException will be thrown when terminal operation executes. If you do not implement this marker interface you may use an overloaded sorted version that accepts Comparator as an argument:

Stream.of("barbora", "daria", "cristopher", "adam", "fritz")
    .sorted((s1, s2) -> {
        return s1.compareTo(s2);
    }).forEach(System.out::println);
While

自Java 9发行以来,还添加了以下两种方法:dropWhile和采取。 两者都是接受的中间操作谓语有条件的。

  • dropWhile=删除与给定谓词匹配的元素的最长前缀后,将生成由该流的其余元素组成的流。采取=产生一个流,该流由从该流中获取的,匹配给定谓词的元素的最长前缀组成。

NB两者都可以下令流。

看看下面的示例代码片段:

Set<Integer> numbers = Set.of(1,2,3,4,5,6,7,8);
numbers.stream()
    .takeWhile(x-> x < 5)
    .forEach(System.out::println);
Limit

我们将在这篇文章中观察到的最后一个中间操作是限制. It produces a stream consisting of the elements, 限制ed to be no 长er than specified length. This method accepts one argument - 长代表所需长度的值。

List<Integer> numbers = Arrays.asList(-9, -18, 0, 12, -5, 92, 13, 50, -75, 25, 4); 
numbers.stream().sorted().limit(5).forEach(System.out::println); 

Terminal operations

另一组操作称为终端操作。 与中间操作相比,在流上仅执行一个终端操作,因为在执行该操作之后,流管道将被消耗,并且无法再使用。 终端操作会产生一些结果,而不是流:

我们将在此处探讨几种值得注意的终端操作。

For each

之前,我们在大多数示例中都使用了此操作。 该方法接受消费者定义要在流的每个元素上执行的操作的函数。 您还记得,在文章开头,我们比较了完成此任务的两种方法:

List<String> names = Arrays.asList("Anna", "Bob", "Carolina", "Denis", "Anna", "Jack", "Marketa", "Simon", "Anna");

for (String name: names){
    if (name.equalsIgnoreCase("Anna")){
        System.out.println(name);
    }
}

names.stream().filter(name->name.equalsIgnoreCase("Anna")).forEach(System.out::println);

我们也在这里使用方法参考使代码简短易读。 完整的样子如下:

stream.forEach(name->System.out.println(name));

NB对于并行流管道,此操作不能保证遵守流的遇到顺序,因为这样做会牺牲并行性的好处。 对于任何给定的元素,可以在库选择的任何时间和线程中执行操作。 如果操作访问共享状态,则它负责提供所需的同步。

Collect

The previous terminal operation has no return: it consumes data, but does not provide something back. However, often we need to perform some stream operation on collection and then get changed collection back. In these situations we use collect method. It does a mutable reduction operation on the elements of this stream using collector.

有两个的重载版本收藏方法:返回一个单结果,而另一个返回收藏ion。 让我们详细看一下:

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5); 
List<Integer> result = numbers.collect(Collectors.toList()); 

在此代码段中,我们使用内置的收藏家收集流到列表的方法。 Java开箱即用提供了其他有用的方法:

  • Collectors.toMapCollectors.toSet
Find

Finally there are operations that return Optional object. I group them together, while they are separate methods. Let list them first:

  • findAnyfindFirst

他们两个都没有任何论点,因此您可能会问一个非常合理的问题:他们如何实际找到数据?。 这些方法与过滤,我们之前已经介绍过。 看一个例子:

List<String> names = Arrays.asList("anna", "barbora", "andrew", "benjamin", "carol");

Optional<String> anna = names.stream().filter(name->name.equalsIgnoreCase("anna")).findFirst();
if (anna.isPresent){
    System.out.println("Anna is here!");
} else {
    System.out.println("No Anna there");
}

在这里我们用findFirst与...结合过滤 to find a matching result. However, this is a very artificial example: usually we don't do this, but 过滤 by some 图案:

// names list

names.stream().filter(name->name.startsWith("A")).findAny().ifPresent(System.out::println);

在这两种情况下,我们都有安娜。 这两种方法有什么区别? 顾名思义,findFirst =返回匹配的元素第一次发生。 就我们而言,他们都是安娜。 findAny返回任何 matching element, that can be first or can be not: behavior of this operation is explicitly nondeterministic; it is free to select 任何 element in the stream.

We did a comprehensive review of Java Stream and described most notable methods every developer should know (also this is not complete list, feel free to explore Javadoc). Stream is a concept, borrowed from functional programming languages and better works with other Java functional tools, like optionals. Many developers, however, find them not enough powerful. As alternative to built-in Java tools, we can use Vavr library. In previous parts of this trilogy, I already described Option and Try classes.

Conclusion

This post explored to a subject of streams - a concept that came from functional programming languages. Technically, Java stream is a sequence of elements supporting sequential and parallel aggregate operations. Java started to include Streams API as a part of JDK 8 and has improved them in subsequent releases. Also, Java streams are not only ones. Vavr library that offers additions to functional Java also supplies own streams that are a bit different from vanilla Java. During this post we observed core tasks connected to Java streams: creation, building pipelines. Have a nice day!

from: https://dev.to//andreevich/introduction-to-java-streams-4k20

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值