# Java 流

原文：http://zetcode.com/lang/java/streams/

在 Java 教程的这一部分中，我们将使用流。 流极大地改善了 Java 中数据的处理。

## Java 流定义

流是来自源的一系列元素，支持顺序和并行聚合操作。 常见的聚合操作是：过滤，映射，缩小，查找，匹配和排序。 源可以是将数据提供给流的集合，IO 操作或数组。

Java 集合是一种内存中的数据结构，所有元素都包含在内存中，而流是一种数据结构，其中的所有元素都是按需计算的。 与显式迭代的集合（外部迭代）相反，流操作为我们在后台进行迭代。 从 Java8 开始，Java 集合具有`stream()`方法，该方法从集合中返回流。

`Stream`接口在`java.util.stream`包中定义。

对流进行的操作会在不修改其源的情况下产生结果。

## 流的特征

*   流不存储数据； 相反，它们从诸如集合，数组或 IO 通道之类的源中提供数据。
*   流不修改数据源。 例如，在执行过滤操作时，它们会将数据转换为新的流。
*   许多流操作是延迟求值的。 这允许自动代码优化和短路求值。
*   流可以是无限的。 诸如`limit()`之类的方法使我们可以从无限流中获得一些结果。
*   在流的生存期内，流的元素只能访问一次。 像`Iterator`一样，必须生成新的流以重新访问源中的相同元素。
*   流具有用于流元素内部迭代的方法，例如`forEach()`和`forEachOrdered()`。
*   流支持类似 SQL 的操作和常用函数式操作，例如过滤，映射，缩小，查找，匹配和排序。

## Java 流管道

流管道由源，中间操作和终端操作组成。 中间操作返回新的修改后的流； 因此，可以链接多个中间操作。 另一方面，终端操作返回`void`或一个值。 终端操作后，将无法再使用该流。 使终端操作短路意味着流可以在处理所有值之前终止。 如果流是无限的，这很有用。

中间操作是懒惰的。 在执行终端操作之前，它们不会被调用。 当我们处理较大的数据流时，这可以提高性能。

## Java 创建流

流是从各种源创建的，例如集合，数组，字符串，IO 资源或生成器。

`CreatingStreams.java`

```java
package com.zetcode;

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

public class CreatingStreams {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("pen", "coin", "desk", "chair");

        String word = words.stream().findFirst().get();
        System.out.println(word);

        Stream<String> letters = Arrays.stream(new String[]{ "a", "b", "c"});        
        System.out.printf("There are %d letters%n", letters.count());

        String day = "Sunday";
        IntStream istr = day.codePoints();

        String s = istr.filter(e -> e != 'n').collect(StringBuilder::new,
                StringBuilder::appendCodePoint, StringBuilder::append).toString();
        System.out.println(s);
    }
}

```

在此示例中，我们使用从列表，数组和字符串创建的流。

```java
List<String> words = Arrays.asList("pen", "coin", "desk", "chair");

```

将创建一个字符串列表。

```java
String word = words.stream().findFirst().get();

```

使用`stream`方法，我们从列表集合创建一个流。 在流上，我们调用`findFirst()`方法，该方法返回流的第一个元素。 （它返回一个`Optional`，我们使用`get()`方法从中获取值。）

```java
Stream<String> letters = Arrays.stream(new String[]{ "a", "b", "c"});        
System.out.printf("There are %d letters%n", letters.count());

```

我们从数组创建流。 流的`count()`方法返回流中的元素数。

```java
String day = "Sunday";
IntStream istr = day.codePoints();

String s = istr.filter(e -> e != 'n').collect(StringBuilder::new,
        StringBuilder::appendCodePoint, StringBuilder::append).toString();
System.out.println(s);

```

在这里，我们从字符串创建流。 我们过滤字符并从过滤的字符构建新的字符串。

```java
$ java com.zetcode.CreatingStreams
pen
There are 3 letters
Suday

```

这是输出。

`Stream`有三种数值流：`IntStream`，`DoubleStream`和`LongStream`。

`CreatingStreams2.java`

```java
package com.zetcode;

import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

public class CreatingStreams2 {

    public static void main(String[] args) {

        IntStream integers = IntStream.rangeClosed(1, 16);
        System.out.println(integers.average().getAsDouble());

        DoubleStream doubles = DoubleStream.of(2.3, 33.1, 45.3);
        doubles.forEachOrdered(e -> System.out.println(e));

        LongStream longs = LongStream.range(6, 25);
        System.out.println(longs.count());
    }
}

```

该示例适用于上述三个类。

```java
IntStream integers = IntStream.rangeClosed(1, 16);
System.out.println(integers.average().getAsDouble());

```

使用`IntStream.rangeClosed()`方法创建整数流。 我们将其平均值打印到控制台。

```java
DoubleStream doubles = DoubleStream.of(2.3, 33.1, 45.3);
doubles.forEachOrdered(e -> System.out.println(e));

```

使用`DoubleStream.of()`方法创建双精度值流。 我们使用`forEachOrdered()`方法将元素的有序列表打印到控制台。

```java
LongStream longs = LongStream.range(6, 25);
System.out.println(longs.count());

```

用`LongStream.range()`方法创建一个长整数的字符串。 我们使用`count()`方法打印元素的数量。

```java
$ java com.zetcode.CreatingStreams2
8.5
2.3
33.1
45.3
19

```

这是示例的输出。

`Stream.of()`方法返回其元素为指定值的顺序有序流。

`CreatingStreams3.java`

```java
package com.zetcode;

import java.util.Comparator;
import java.util.stream.Stream;

public class CreatingStreams3 {

    public static void main(String[] args) {

        Stream<String> colours = Stream.of("red", "green", "blue");
        String col = colours.skip(2).findFirst().get();    
        System.out.println(col);

        Stream<Integer> nums = Stream.of(3, 4, 5, 6, 7);
        int maxVal = nums.max(Comparator.naturalOrder()).get();
        System.out.println(maxVal);
    }
}

```

在示例中，我们使用`Stream.of()`方法创建两个流。

```java
Stream<String> colours = Stream.of("red", "green", "blue");

```

将创建三个字符串流。

```java
String col = colours.skip(2).findFirst().get();    

```

使用`skip()`方法，我们跳过了两个元素，而使用`findFirst()`方法只找到了一个元素。

```java
Stream<Integer> nums = Stream.of(3, 4, 5, 6, 7);
int maxVal = nums.max(Comparator.naturalOrder()).get();

```

我们创建一个整数流并找到其最大数目。输出如下：

```java
$ java com.zetcode.CreatingStreams3
blue
7

```

创建流的其他方法是：`Stream.iterate()`和`Stream.generate()`。

`CreatingStreams4.java`

```java
package com.zetcode;

import java.util.Random;
import java.util.stream.Stream;

public class CreatingStreams4 {

    public static void main(String[] args) {

        Stream<Integer> s1 = Stream.iterate(5, n -> n * 2).limit(10);
        s1.forEach(System.out::println);

        Stream.generate(new Random()::nextDouble)
                .map(e -> (e * 10))
                .limit(5)
                .forEach(System.out::println);        
    }
}

```

在示例中，我们使用`Stream.iterate()`和`Stream.generate()`创建两个流。

```java
Stream<Integer> s1 = Stream.iterate(5, n -> n * 2).limit(10);
s1.forEach(System.out::println);

```

`Stream.iterate()`返回通过将函数迭代应用到初始元素而产生的无限顺序有序流。 初始元素称为种子。 通过将函数应用于第一个元素来生成第二个元素。 通过将函数应用于第二个元素等来生成第三个元素。

```java
Stream.generate(new Random()::nextDouble)
        .map(e -> (e * 10))
        .limit(5)
        .forEach(System.out::println); 

```

使用`Stream.generate()`方法创建五个随机双打的流。 每个元素乘以十。 最后，我们遍历流并将每个元素打印到控制台。输出如下：

```java
$ java com.zetcode.CreatingStreams4
5
10
20
40
80
160
320
640
1280
2560
8.704675577530493
5.732011478196306
3.8978402578067515
3.6986033299500933
6.0976417139147205

```


可以从文件创建流。

`CreatingStreams5.java`

```java
package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class CreatingStreams5 {

    public static void main(String[] args) throws IOException {

        Path path = Paths.get("/home/janbodnar/myfile.txt");
        Stream<String> stream = Files.lines(path);

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

```

该示例读取文件并使用流打印其内容。

```java
Path path = Paths.get("/home/janbodnar/myfile.txt");

```

使用`Paths.get()`方法创建`Path`对象。 `Path`对象用于在文件系统中定位文件。

```java
Stream<String> stream = Files.lines(path);

```

从路径开始，我们使用`Files.lines()`方法创建一个流； 流的每个元素都是文件中的一行。

```java
stream.forEach(System.out::println);

```

我们浏览流中的元素并将它们打印到控制台。

## 内部和外部迭代

根据谁控制迭代过程，我们区分外部和内部迭代。 外部迭代，也称为活动或显式迭代，由程序员处理。 在 Java8 之前，它是 Java 中唯一的迭代类型。 对于外部迭代，我们使用`for`和`while`循环。 内部迭代（也称为被动迭代或隐式迭代）由迭代器本身控制。 Java 流中提供了内部迭代。

`ExternalIteration.java`

```java
package com.zetcode;

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

public class ExternalIteration {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("pen", "coin", "desk", 
                "eye", "bottle");

        Iterator it = words.iterator();

        while (it.hasNext()) {

            System.out.println(it.next());
        }
    }
}

```

在代码示例中，我们从字符串列表中检索和迭代器对象。 在`while`循环中使用迭代器的`hasNext()`和`next()`方法，我们迭代列表的元素。

在下面的示例中，我们使用外部迭代来迭代相同的列表。

`InternalIteration.java`

```java
package com.zetcode;

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

public class InternalIteration {

    public static void main(String[] args) {

        List<String> words = Arrays.asList("pen", "coin", "desk",
                "eye", "bottle");

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

```

在示例中，我们从列表创建流。 我们使用流的`forEach()`在内部对流元素进行迭代。

## Java 流过滤器

过滤数据流是流最重要的功能之一。 `filter()`方法是一个中间操作，它返回由与给定谓词匹配的流元素组成的流。 谓词是一种返回布尔值的方法。

`FilterStream.java`

```java
package com.zetcode;

import java.util.Arrays;
import java.util.stream.IntStream;

public class FilterStream {

    public static void main(String[] args) {

        IntStream nums = IntStream.rangeClosed(0, 25);

        int[] vals = nums.filter(e -> e > 15).toArray();

        System.out.println(Arrays.toString(vals));
    }
}

```

该代码示例创建一个整数流。 流被过滤为仅包含大于 15 的值。

```java
IntStream nums = IntStream.rangeClosed(0, 25);

```

使用`IntStream`，创建了 26 个整数的流。 `rangeClose()`方法从两个值的边界创建整数流； 这两个值（开始和结束）都包含在范围内。

```java
int[] vals = nums.filter(e -> e > 15).toArray();

```

我们将 lambda 表达式（`e -> e > 15`）传递给`filter()`函数； 对于大于 15 的值，该表达式返回`true`。`toArray()`是将流转换为整数数组的终端操作。

```java
System.out.println(Arrays.toString(vals));

```

将数组打印到控制台。

```java
$ java com.zetcode.FilterStream
[16, 17, 18, 19, 20, 21, 22, 23, 24, 25]

```

该示例产生此输出。

下一个示例生成事件编号列表。

`FilterStream2.java`

```java
package com.zetcode;

import java.util.stream.IntStream;

public class FilterStream2 {

    public static void main(String[] args) {

        IntStream nums = IntStream.rangeClosed(0, 30);
        nums.filter(FilterStream2::isEven).forEach(System.out::println);
    }

    private static boolean isEven(int e) {

        return e % 2 == 0;
    }
}

```

为了从流中获得偶数，我们将`isEven()`方法引用传递给`filter()`方法。

```java
nums.filter(FilterStream2::isEven).forEach(System.out::println);

```

双冒号 `::` 运算符用于传递方法引用。 `forEach()`方法是对流的元素进行迭代的终端操作。 它引用了`System.out.println()`方法的方法。

## 跳过和限制元素

`skip(n)`方法跳过流的前`n`个元素，`limit(m)`方法将流中的元素数限制为`m`。

`SkipLimit.java`

```java
package com.zetcode;

import java.util.stream.IntStream;

public class SkipLimit {

    public static void main(String[] args) {

        IntStream s = IntStream.range(0, 15);
        s.skip(3).limit(5).forEach(System.out::println);
    }
}

```

该示例创建了一个十五个整数的流。 我们使用`skip()`方法跳过前三个元素，并将元素个数限制为 5。输出如下：

```java
$ java com.zetcode.SkipLimit
3
4
5
6
7

```


## Java 流排序元素

`sorted()`方法根据提供的`Comparator`对该流的元素进行排序。

`Sorting.java`

```java
package com.zetcode;

import java.util.Comparator;
import java.util.stream.IntStream;

public class Sorting {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(4, 3, 2, 1, 8, 6, 7, 5);

        nums.boxed().sorted(Comparator.reverseOrder())
                .forEach(System.out::println);
    }
}

```

该示例按降序对整数元素进行排序。 `boxed()`方法将`IntStream`转换为`Stream<Integer>`。输出如下：

```java
$ java com.zetcode.Sorting
8
7
6
5
4
3
2
1

```

下一个示例显示如何比较对象流。

`Sorting2.java`

```java
package com.zetcode;

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

class Car {

    private String name;
    private int price;

    public Car(String name, int price ) {

        this.name = name;
        this.price = price;
    }    

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" + "name=" + name + ", price=" + price + '}';
    }
}

public class Sorting2 {

    public static void main(String[] args) {

        List<Car> cars = Arrays.asList(new Car("Citroen", 23000), 
                new Car("Porsche", 65000), new Car("Skoda", 18000), 
                new Car("Volkswagen", 33000), new Car("Volvo", 47000));

        cars.stream().sorted(Comparator.comparing(Car::getPrice))
                .forEach(System.out::println);
    }
}

```

该示例按价格对汽车进行排序。

```java
List<Car> cars = Arrays.asList(new Car("Citroen", 23000), 
        new Car("Porsche", 65000), new Car("Skoda", 18000), 
        new Car("Volkswagen", 33000), new Car("Volvo", 47000));

```

将创建汽车列表。

```java
cars.stream().sorted(Comparator.comparing(Car::getPrice))
        .forEach(System.out::println);

```

使用`stream()`方法从列表中生成流。 我们传递了`Car`的`getPrice()`方法的引用，该方法在按汽车价格进行比较时使用。输出如下：

```java
$ java com.zetcode.Sorting2
Car{name=Skoda, price=18000}
Car{name=Citroen, price=23000}
Car{name=Volkswagen, price=33000}
Car{name=Volvo, price=47000}
Car{name=Porsche, price=65000}

```

## Java 流唯一值

`distinct()`方法返回由唯一元素组成的流。

`UniqueElements.java`

```java
package com.zetcode;

import java.util.Arrays;
import java.util.stream.IntStream;

public class UniqueElements {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(1, 1, 3, 4, 4, 6, 7, 7);
        int a[] = nums.distinct().toArray();

        System.out.println(Arrays.toString(a));
    }
}

```

该示例从整数流中删除重复的值。

```java
IntStream nums = IntStream.of(1, 1, 3, 4, 4, 6, 7, 7);

```

流中有三个重复的值。

```java
int a[] = nums.distinct().toArray();

```

我们使用`distinct()`方法删除重复项。输出如下：

```java
$ java com.zetcode.UniqueElements
[1, 3, 4, 6, 7]

```

## Java 流映射

可以将元素更改为新的流； 原始来源未修改。 `map()`方法返回一个流，该流由将给定函数应用于流的元素的结果组成。 `map()`是一个中间操作。

`Mapping.java`

```java
package com.zetcode;

import java.util.Arrays;
import java.util.stream.IntStream;

public class Mapping {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8);

        int[] squares = nums.map(e -> e * e).toArray();

        System.out.println(Arrays.toString(squares));
    }
}

```

我们在流的每个元素上映射一个转换函数。

```java
int[] squares = nums.map(e -> e * e).toArray();

```

我们在流上应用一个 lambda 表达式（`e -> e * e`）：每个元素都是平方的。 创建一个新的流，并使用`toArray()`方法将其转换为数组。输出如下：

```java
$ java com.zetcode.Mapping
[1, 4, 9, 16, 25, 36, 49, 64]

```


在下一个示例中，我们转换字符串流。

`Mapping2.java`

```java
package com.zetcode;

import java.util.stream.Stream;

public class Mapping2 {

    public static void main(String[] args) {

        Stream<String> words = Stream.of("cardinal", "pen", "coin", "globe");
        words.map(Mapping2::capitalize).forEach(System.out::println);
    }

    private static String capitalize(String word) {

        word = word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();
        return word;
    }
}

```

我们有一串串的字符串。 我们将流中的每个字符串都大写。

```java
words.map(Mapping2::capitalize).forEach(System.out::println);

```

我们将对`capitalize()`方法的引用传递给`map()`方法。输出如下：

```java
$ java com.zetcode.Mapping2
Cardinal
Pen
Coin
Globe

```

## Java 流归约

归约是将流聚合为类或原始类型的终端操作。

`Reduction.java`

```java
package com.zetcode;

import java.util.stream.IntStream;

public class Reduction {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8);

        int maxValue = nums.max().getAsInt();

        System.out.printf("The maximum value is: %d%n", maxValue);
    }
}

```

从整数流中获取最大值是一种归约运算。

```java
int maxValue = nums.max().getAsInt();

```

使用`max()`方法，我们获得了流的最大元素。 该方法返回一个`Optional`，使用`getAsInt()`方法从中获得整数。输出如下：

```java
$ java com.zetcode.Reduction
The maximum value is: 8

```


可以使用`reduce()`方法创建自定义归约。

`Reduction2.java`

```java
package com.zetcode;

import java.util.stream.IntStream;

public class Reduction2 {

    public static void main(String[] args) {

        IntStream nums = IntStream.of(1, 2, 3, 4, 5, 6, 7, 8);

        int product = nums.reduce((a, b) -> a * b).getAsInt();

        System.out.printf("The product is: %d%n", product);
    }
}

```

该示例返回流中整数元素的乘积。输出如下：

```java
$ java com.zetcode.Reduction
The product is: 40320

```


## Java 流收集操作

收集是一种终端归约操作，可将流的元素还原为 Java 集合，字符串，值或特定的分组。

`Collecting.java`

```java
package com.zetcode;

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

class Car {

    private String name;
    private int price;

    public Car(String name, int price) {

        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" + "name=" + name + ", price=" + price + '}';
    }
}

public class Collecting {

    public static void main(String[] args) {

        List<Car> cars = Arrays.asList(new Car("Citroen", 23000),
                new Car("Porsche", 65000), new Car("Skoda", 18000),
                new Car("Volkswagen", 33000), new Car("Volvo", 47000));

        List<String> names = cars.stream().map(Car::getName)
                .filter(name -> name.startsWith("Vo"))
                .collect(Collectors.toList());

        for (String name: names) {
            System.out.println(name);
        }
    }
}

```

该示例从汽车对象列表创建流，按汽车名称过滤汽车，并返回匹配的汽车名称列表。

```java
List<String> names = cars.stream().map(Car::getName)
        .filter(name -> name.startsWith("Vo"))
        .collect(Collectors.toList());

```

在管道的最后，我们使用`collect()`方法进行转换。 输出如下：

```java
$ java com.zetcode.Collecting
Volkswagen
Volvo

```


在下一个示例中，我们使用`collect()`方法对数据进行分组。

`Collecting2.java`

```java
package com.zetcode;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Collecting2 {

    public static void main(String[] args) {

        List<String> items = Arrays.asList("pen", "book", "pen", "coin",
                "book", "desk", "book", "pen", "book", "coin");

        Map<String, Long> result = items.stream().collect(
                Collectors.groupingBy(
                        Function.identity(), Collectors.counting()
                ));

        for (Map.Entry<String, Long> entry : result.entrySet()) {

            String key = entry.getKey();
            Long value = entry.getValue();

            System.out.format("%s: %d%n", key, value);
        }
    }
}

```

该代码示例按元素在流中的出现将其分组。

```java
Map<String, Long> result = items.stream().collect(
        Collectors.groupingBy(
                Function.identity(), Collectors.counting()
        ));

```

使用`Collectors.groupingBy()`方法，我们可以计算流中元素的出现次数。 该操作返回一个映射。

```java
for (Map.Entry<String, Long> entry : result.entrySet()) {

    String key = entry.getKey();
    Long value = entry.getValue();

    System.out.format("%s: %d%n", key, value);
}

```

我们浏览映射并打印其键/值对。输出如下：

```java
$ java com.zetcode.Collecting2
desk: 1
book: 4
pen: 3
coin: 2

```

Java 教程的这一部分介绍了流。