ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 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 教程的这一部分介绍了流。