Java 8 Stream - Java Stream
欢迎阅读 Java 8 Stream API 教程。在最近的几篇 Java 8 文章中,我们研究了Java 8 接口变化、功能接口和 Lambda 表达式。今天,我们将研究 Java 8 中引入的主要 API 之一 - Java Stream。
Java 8 流
- Java 8 流
- 集合和 Java 流
- Java 8 Stream 中的函数接口
- java.util.Optional
- java.util.Spliterator
- Java 流的中间和终端操作
- Java 流短路操作
- Java 流示例
- Java 8 Stream API 限制
Java 流
在研究 Java Stream API 示例之前,让我们先看看为什么需要它。假设我们想要遍历一个整数列表并找出所有大于 10 的整数的总和。在 Java 8 之前,这样做的方法是:
private static int sumIterator(List<Integer> list) {
Iterator<Integer> it = list.iterator();
int sum = 0;
while (it.hasNext()) {
int num = it.next();
if (num > 10) {
sum += num;
}
}
return sum;
}
上述方法有三个主要问题:
- 我们只想知道整数的总和,但我们还必须提供迭代如何进行,这也称为外部迭代,因为客户端程序正在处理迭代列表的算法。
- 该程序本质上是顺序的,我们无法轻松地并行执行此操作。
- 即使完成一个简单的任务也需要大量的代码。
为了克服上述所有缺点,引入了 Java 8 Stream API。我们可以使用 Java Stream API 来实现内部迭代,这样更好,因为 Java 框架可以控制迭代。内部迭代提供了几个功能,例如顺序和并行执行、基于给定条件的过滤、映射等。大多数 Java 8 Stream API 方法参数都是函数接口,因此 lambda 表达式可以很好地与它们配合使用。让我们看看如何使用 Java Streams 在一行语句中编写上述逻辑。
private static int sumStream(List<Integer> list) {
return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum();
}
请注意,上述程序利用了 Java 框架迭代策略、过滤和映射方法,可以提高效率。首先,我们将研究 Java 8 Stream API 的核心概念,然后通过一些示例来了解最常用的方法。
集合和 Java 流
集合是一种用于保存值的内存数据结构,在开始使用集合之前,所有值都应该已填充。而 Java Stream 是一种按需计算的数据结构。Java Stream 不存储数据,它对源数据结构(集合和数组)进行操作并生成我们可以使用和执行特定操作的流水线数据。例如,我们可以从列表中创建一个流并根据条件对其进行过滤。Java Stream 操作使用函数式接口,这使其非常适合使用 lambda 表达式进行函数式编程。如您在上面的示例中所见,使用 lambda 表达式使我们的代码可读且简短。Java 8 Stream 内部迭代原理有助于在某些流操作中实现惰性查找。例如,可以惰性地实现过滤、映射或重复删除,从而实现更高的性能和优化范围。Java Stream 是可消耗的,因此无法创建对流的引用以备将来使用。由于数据是按需的,因此不可能多次重用同一个流。 Java 8 Stream 支持顺序和并行处理,并行处理对于实现大型集合的高性能非常有帮助。所有 Java Stream API 接口和类都在包中java.util.stream
。由于我们可以使用自动装箱在集合中使用原始数据类型(例如 int、long),并且这些操作可能需要大量时间,因此有针对原始类型的特定类 - IntStream
、LongStream
和DoubleStream
。
Java 8 Stream 中的函数接口
Java 8 Stream API 方法中的一些常用的功能接口是:
- 函数和 BiFunction:函数表示接受一种类型的参数并返回另一种类型的参数的函数。是通用形式,其中T 是函数输入的类型,R 是函数结果的类型。对于处理原始类型,有特定的函数接口 - 、、、、、、、、、、等
Function<T, R>
。使用ToIntFunction
原始特化的 一些Stream 方法包括:ToLongFunction
ToDoubleFunction
ToIntBiFunction
ToLongBiFunction
ToDoubleBiFunction
LongToIntFunction
LongToDoubleFunction
IntToLongFunction
IntToDoubleFunction
Function
- <R> 流<R> 映射(函数<? super T, ? extends R> 映射器)
- IntStream mapToInt(ToIntFunction<? super T> mapper) - 类似地,对于长整型和双整型返回原始特定流。
- IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper) - 类似地适用于 long 和 double
- <A> A[] toArray(IntFunction<A[]> 生成器)
- <U> U 减少(U 身份,BiFunction<U,?super T,U> 累加器,BinaryOperator<U> 组合器)
- Predicate 和 BiPredicate:它表示用来测试流元素的谓词。这用于从 Java 流中过滤元素。就像,有针对 int、long 和 double 的原始特定接口。使用
或特化
Function
的某些 Stream 方法包括:Predicate
BiPredicate
- 流 <T> 过滤器(谓词 <? super T> 谓词)
- 布尔任何匹配(谓词<?超级T>谓词)
- 布尔 allMatch (谓词 <?超级 T> 谓词)
- 布尔值 noneMatch (谓词 <? super T> 谓词)
- Consumer 和 BiConsumer:它表示接受单个输入参数并且不返回结果的操作。它可用于对 Java 流的所有元素执行某些操作。使用 Java 8 Stream 方法或其原始专业化接口的一些方法
Consumer
包括BiConsumer
:- Stream<T> peek(Consumer<? super T> action)
- void forEach(Consumer<? super T> action)
- void forEachOrdered(消费者<?super T>动作)
- Supplier:Supplier 表示一种操作,通过该操作我们可以在流中生成新值。Stream 中接受参数的一些方法
Supplier
包括:- 公共静态<T> Stream<T> 生成(Supplier<T> s)
- <R> R 收集(供应商<R> 供应商,BiConsumer<R, ? super T> 累加器,BiConsumer<R, R> 组合器)
java.util.Optional
Java Optional 是一个容器对象,可能包含也可能不包含非空值。如果值存在,则isPresent()
返回 true 并get()
返回该值。流终端操作返回 Optional 对象。其中一些方法是:
- 可选<T> 减少(BinaryOperator<T> 累加器)
- 可选 <T> min(比较器<?超级 T> 比较器)
- 可选 <T> max (比较器 <?超级 T> 比较器)
- 可选<T> findFirst()
- 可选<T> findAny()
java.util.Spliterator
为了支持 Java 8 Stream API 中的并行执行,Spliterator
使用了接口。SpliteratortrySplit
方法返回一个新的 Spliterator,它管理原始 Spliterator 元素的子集。
Java 流的中间和终端操作
返回新 Stream 的 Java Stream API 操作称为中间操作。大多数时候,这些操作本质上是惰性的,因此它们开始生成新的流元素并将其发送到下一个操作。中间操作永远不会是产生最终结果的操作。常用的中间操作是filter
和map
。Java 8 Stream API 操作会返回结果或产生副作用。一旦在流上调用终端方法,它就会使用该流,之后我们就不能再使用流了。终端操作本质上是急切的,即,它们在返回结果之前处理流中的所有元素。常用的终端方法是、、、、、、等forEach
。您可以toArray
从返回类型识别终端方法,它们永远不会返回 Stream。min
max
findFirst
anyMatch
allMatch
Java 流短路操作
如果中间操作可以为无限流生成有限流,则该中间操作称为短路。例如limit()
和是两个短路中间操作。如果终端操作可以在有限时间内终止无限流,则该终端操作称为短路。例如,,,和skip()
是短路anyMatch
终端操作。allMatch
noneMatch
findFirst
findAny
Java 流示例
我已经介绍了 Java 8 Stream API 的几乎所有重要部分。使用这个新的 API 功能令人兴奋,让我们通过一些 Java 流示例来了解它的实际作用。
创建 Java 流
我们可以通过多种方式从数组和集合创建 Java 流。让我们通过简单的例子来看一下。
-
我们可以使用
Stream.of()
类似类型的数据来创建流。例如,我们可以从一组 int 或 Integer 对象创建整数的 Java 流。Stream<Integer> stream = Stream.of(1,2,3,4);
-
我们可以使用
Stream.of()
对象数组来返回流。请注意,它不支持自动装箱,因此我们无法传递原始类型数组。Stream<Integer> stream = Stream.of(new Integer[]{1,2,3,4}); //works fine Stream<Integer> stream1 = Stream.of(new int[]{1,2,3,4}); //Compile time error, Type mismatch: cannot convert from Stream<int[]> to Stream<Integer>
-
我们可以使用 Collection
stream()
来创建顺序流和parallelStream()
并行流。List<Integer> myList = new ArrayList<>(); for(int i=0; i<100; i++) myList.add(i); //sequential stream Stream<Integer> sequentialStream = myList.stream(); //parallel stream Stream<Integer> parallelStream = myList.parallelStream();
-
我们可以使用
Stream.generate()
和Stream.iterate()
方法来创建Stream。Stream<String> stream1 = Stream.generate(() -> {return "abc";}); Stream<String> stream2 = Stream.iterate("abc", (i) -> i);
-
使用
Arrays.stream()
和String.chars()
方法。LongStream is = Arrays.stream(new long[]{1,2,3,4}); IntStream is2 = "abc".chars();
将 Java 流转换为集合或数组
我们可以通过多种方法从 java Stream 中获取集合或数组。
-
我们可以使用 java Stream
collect()
方法从流中获取 List、Map 或 Set。Stream<Integer> intStream = Stream.of(1,2,3,4); List<Integer> intList = intStream.collect(Collectors.toList()); System.out.println(intList); //prints [1, 2, 3, 4] intStream = Stream.of(1,2,3,4); //stream is closed, so we need to create it again Map<Integer,Integer> intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10)); System.out.println(intMap); //prints {1=11, 2=12, 3=13, 4=14}
-
我们可以使用流
toArray()
方法从流中创建一个数组。Stream<Integer> intStream = Stream.of(1,2,3,4); Integer[] intArray = intStream.toArray(Integer[]::new); System.out.println(Arrays.toString(intArray)); //prints [1, 2, 3, 4]
Java 流中间操作
让我们看一下常用的 java Stream 中间操作示例。
-
流过滤器()示例:我们可以使用过滤器()方法来测试流元素的条件并生成过滤列表。
List<Integer> myList = new ArrayList<>(); for(int i=0; i<100; i++) myList.add(i); Stream<Integer> sequentialStream = myList.stream(); Stream<Integer> highNums = sequentialStream.filter(p -> p > 90); //filter numbers greater than 90 System.out.print("High Nums greater than 90="); highNums.forEach(p -> System.out.print(p+" ")); //prints "High Nums greater than 90=91 92 93 94 95 96 97 98 99 "
-
流 map() 示例:我们可以使用 map() 将函数应用于流。让我们看看如何使用它将大写函数应用于字符串列表。
Stream<String> names = Stream.of("aBc", "d", "ef"); System.out.println(names.map(s -> { return s.toUpperCase(); }).collect(Collectors.toList())); //prints [ABC, D, EF]
-
流 sorted() 示例:我们可以通过传递 Comparator 参数来使用 sorted() 对流元素进行排序。
Stream<String> names2 = Stream.of("aBc", "d", "ef", "123456"); List<String> reverseSorted = names2.sorted(Comparator.reverseOrder()).collect(Collectors.toList()); System.out.println(reverseSorted); // [ef, d, aBc, 123456] Stream<String> names3 = Stream.of("aBc", "d", "ef", "123456"); List<String> naturalSorted = names3.sorted().collect(Collectors.toList()); System.out.println(naturalSorted); //[123456, aBc, d, ef]
-
Stream flatMap() 示例:我们可以使用 flatMap() 从列表流中创建一个流。让我们看一个简单的例子来消除这个疑虑。
Stream<List<String>> namesOriginalList = Stream.of( Arrays.asList("Pankaj"), Arrays.asList("David", "Lisa"), Arrays.asList("Amit")); //flat the stream from List<String> to String stream Stream<String> flatStream = namesOriginalList .flatMap(strList -> strList.stream()); flatStream.forEach(System.out::println);
Java 流终端操作
让我们看一些 java 流终端操作示例。
-
流 Reduce() 示例:我们可以使用 Reduce() 对流中的元素执行缩减,使用关联累积函数,并返回一个可选值。让我们看看如何使用它来乘以流中的整数。
Stream<Integer> numbers = Stream.of(1,2,3,4,5); Optional<Integer> intOptional = numbers.reduce((i,j) -> {return i*j;}); if(intOptional.isPresent()) System.out.println("Multiplication = "+intOptional.get()); //120
-
流 count() 示例:我们可以使用此终端操作来计算流中的项目数量。
Stream<Integer> numbers1 = Stream.of(1,2,3,4,5); System.out.println("Number of elements in stream="+numbers1.count()); //5
-
Stream forEach() 示例:这可用于迭代流。我们可以使用它代替迭代器。让我们看看如何使用它来打印流的所有元素。
Stream<Integer> numbers2 = Stream.of(1,2,3,4,5); numbers2.forEach(i -> System.out.print(i+",")); //1,2,3,4,5,
-
Stream match() 示例:让我们看一下 Stream API 中匹配方法的一些示例。
Stream<Integer> numbers3 = Stream.of(1,2,3,4,5); System.out.println("Stream contains 4? "+numbers3.anyMatch(i -> i==4)); //Stream contains 4? true Stream<Integer> numbers4 = Stream.of(1,2,3,4,5); System.out.println("Stream contains all elements less than 10? "+numbers4.allMatch(i -> i<10)); //Stream contains all elements less than 10? true Stream<Integer> numbers5 = Stream.of(1,2,3,4,5); System.out.println("Stream doesn't contain 10? "+numbers5.noneMatch(i -> i==10)); //Stream doesn't contain 10? true
-
流 findFirst() 示例:这是一个短路终端操作,让我们看看如何使用它从以 D 开头的流中找到第一个字符串。
Stream<String> names4 = Stream.of("Pankaj","Amit","David", "Lisa"); Optional<String> firstNameWithD = names4.filter(i -> i.startsWith("D")).findFirst(); if(firstNameWithD.isPresent()){ System.out.println("First Name starting with D="+firstNameWithD.get()); //David }
Java 8 Stream API 限制
Java 8 Stream API 带来了许多新内容来处理列表和数组,但它也有一些限制。
-
无状态 lambda 表达式:如果您使用并行流并且 lambda 表达式是有状态的,则可能会导致随机响应。让我们用一个简单的程序来看看。
StatefulParallelStream.java
package com.journaldev.java8.stream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class StatefulParallelStream { public static void main(String[] args) { List<Integer> ss = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); List<Integer> result = new ArrayList<Integer>(); Stream<Integer> stream = ss.parallelStream(); stream.map(s -> { synchronized (result) { if (result.size() < 10) { result.add(s); } } return s; }).forEach( e -> {}); System.out.println(result); } }
如果我们运行上述程序,您将得到不同的结果,因为这取决于流的迭代方式,并且我们没有为并行处理定义任何顺序。如果我们使用顺序流,则不会出现此问题。
-
一旦 Stream 被使用,以后就不能再使用了。正如您在上面的示例中看到的那样,每次我创建一个 Stream 时都会发生这种情况。
-
Stream API 中有很多方法,最令人困惑的是重载方法。这让学习过程变得很耗时。
这就是 Java 8 Stream 示例教程的全部内容。我期待使用此功能并通过并行处理使代码更易读且性能更好。参考:Java Stream API 文档