Java 8 流 – Java 流
欢迎来到Java 8 Stream API教程。在过去的几篇Java 8文章中,我们学习了Java 8接口的变化、函数式接口和Lambda表达式。今天我们将介绍Java 8中引入的一个重要API – Java Stream。
Java 8 流
- Java 8 流
集合和 Java 流
Java 8 流中的函数式接口
函数和双函数
断言和双断言
消费者和双消费者
供应商
java.util.Optional
java.util.Spliterator
Java 流的中间和终端操作
Java 流的短路操作
Java 流示例
创建 Java 流
将 Java 流转换为集合或数组
Java 流的中间操作
Java 流的终端操作
Java 8 流 API 的限制
Java Stream (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 Streams是可消耗的,因此无法为将来的使用创建流的引用。由于数据是按需获取的,因此无法多次重用同一流。Java 8 Stream支持顺序处理和并行处理,对于大型集合,使用并行处理可以帮助实现高性能。所有Java Stream API的接口和类都在java.util.stream包中。由于我们可以使用原始数据类型(例如int、long)在集合中使用自动装箱,而这些操作可能需要很长时间,因此针对原始类型有特定的类-IntStream、LongStream和DoubleStream。
Java 8流中的函数式接口
Java 8 Stream API方法中常用的函数式接口包括:
- 功能和BiFunction:Function表示接受一种类型的参数并返回另一种类型的参数的函数。Function
IntStream mapToInt(ToIntFunction super T> mapper)-类似的对于long和double返回基本类型的流。
IntStream flatMapToInt(Function super T, ? extends IntStream> mapper)-类似的对于long和double
A[] toArray(IntFunction generator)
U reduce(U identity, BiFunction accumulator, BinaryOperator combiner)
Predicate和BiPredicate:它表示针对流元素进行测试的谓词。这用于从java流中筛选元素。就像Function一样,对于int、long和double也有特定的原始接口。一些使用Predicate或BiPredicate特化的Stream方法有:
Stream
boolean anyMatch(Predicate super T> predicate)
boolean allMatch(Predicate super T> predicate)
boolean noneMatch(Predicate super T> predicate)
Consumer和BiConsumer:它表示接受一个输入参数并返回没有结果的操作。它可以用于对java流的所有元素执行某些操作。一些Java 8 Stream方法使用Consumer、BiConsumer或其原始特化接口的情况有:
Stream
void forEach(Consumer super T> action)
void forEachOrdered(Consumer super T> action)
Supplier:Supplier表示通过它可以生成流中的新值的操作。一些Stream中采用Supplier参数的方法有:
public static
Java中的java.util.Optional。
Java Optional是一个容器对象,它可以包含或不包含非空值。如果存在一个值,isPresent()方法将返回true,而get()方法将返回该值。流的终端操作返回Optional对象。其中一些方法包括:
- Optional
reduce(BinaryOperator accumulator) - Optional
min(Comparator super T> comparator) - Optional
max(Comparator super T> comparator) - Optional
findFirst() - Optional
findAny()
java.util.Spliterator 可以被翻译为 “Java中的Spliterator工具类”。
为了支持Java 8 Stream API中的并行执行,使用了Spliterator接口。Spliterator的trySplit方法返回一个管理原始Spliterator元素子集的新Spliterator。
Java流的中间和终端操作
Java Stream API操作中返回新Stream的操作被称为中间操作。大多数情况下,这些操作是惰性的,所以它们开始生成新的流元素并将其发送到下一个操作。中间操作永远不是最终的结果生成操作。常用的中间操作包括filter和map。Java 8 Stream API操作返回一个结果或产生副作用。一旦在流上调用了终端方法,它就会消耗该流,之后我们无法再使用该流。终端操作是急性的,即在返回结果之前处理流中的所有元素。常用的终端方法包括forEach、toArray、min、max、findFirst、anyMatch、allMatch等。您可以根据返回类型识别终端方法,它们永远不会返回一个Stream。
Java Stream 短路操作
如果一个中间操作可能为无限流生成有限流,则称其为“短路”。例如,limit()和skip()是两个短路的中间操作。如果一个终端操作可能在有限时间内终止无限流,则称其为“短路”。例如,anyMatch、allMatch、noneMatch、findFirst和findAny都是短路的终端操作。
Java 流示例
我已经涵盖了Java 8 Stream API的几乎所有重要部分。使用这个新API的特性真是令人兴奋,让我们通过一些Java流示例来看它的实际效果。
创建Java流。
我们可以通过几种方式从数组和集合中创建Java流。让我们通过简单的例子来了解这些方式。
- 我们可以使用Stream.of()从相似类型的数据中创建流。例如,我们可以从一组int或Integer对象中创建Java Stream的整数流。
Stream
我们可以使用Stream.of()和一个对象数组来返回流。请注意,它不支持自动装箱,所以我们不能传递基本类型的数组。
Stream
Stream
我们可以使用Collection的stream()方法创建顺序流,用parallelStream()方法创建并行流。
List
for(int i=0; i<100; i++) myList.add(i);
//顺序流
Stream
//并行流
Stream
我们可以使用Stream.generate()和Stream.iterate()方法创建流。
Stream
Stream
使用Arrays.stream()和String.chars()方法。
LongStream is = Arrays.stream(new long[]{1,2,3,4});
IntStream is2 = “abc”.chars();
将Java流转换为集合或数组
我们可以通过几种方式从Java Stream中获取Collection或Array。
- 我们可以使用Java Stream的collect()方法从流中获取List、Map或Set。
Stream
List
System.out.println(intList); //打印结果为 [1, 2, 3, 4]
intStream = Stream.of(1,2,3,4); //流已关闭,所以需要重新创建
Map
System.out.println(intMap); //打印结果为 {1=11, 2=12, 3=13, 4=14}
我们可以使用stream的toArray()方法从流中创建一个数组。
Stream
Integer[] intArray = intStream.toArray(Integer[]::new);
System.out.println(Arrays.toString(intArray)); //打印结果为 [1, 2, 3, 4]
Java 流的中间操作
让我们来看一下常用的Java流中间操作示例。
- stream filter()示例:我们可以使用filter()方法来测试流元素是否满足条件,并生成一个过滤后的列表。
List
for(int i=0; i<100; i++) myList.add(i); Stream
Stream
System.out.print(“高于90的数字=”);
highNums.forEach(p -> System.out.print(p+” “));
//输出 “高于90的数字=91 92 93 94 95 96 97 98 99 ”
Stream map()示例:我们可以使用map()方法将函数应用于流。让我们看看如何将其应用于一个字符串列表来进行大写转换。
Stream
System.out.println(names.map(s -> {
return s.toUpperCase();
}).collect(Collectors.toList()));
//输出 [ABC, D, EF]
Stream sorted()示例:我们可以使用sorted()方法通过传递Comparator参数来对流元素进行排序。
Stream
List
System.out.println(reverseSorted); // 输出 [ef, d, aBc, 123456]
Stream
List
System.out.println(naturalSorted); //输出 [123456, aBc, d, ef]
Stream flatMap()示例:我们可以使用flatMap()方法从列表流中创建一个流。让我们看一个简单的例子来消除这种疑惑。
Stream> namesOriginalList = Stream.of(
Arrays.asList(“Pankaj”),
Arrays.asList(“David”, “Lisa”),
Arrays.asList(“Amit”));
//将流从List
Stream
.flatMap(strList -> strList.stream());
flatStream.forEach(System.out::println);
Java Stream 终端操作
让我们来看一些Java流终端操作的示例。
- 流reduce()示例:我们可以使用reduce()对流的元素执行约简操作,使用可关联的累积函数,并返回一个Optional。让我们看看如何使用它来对流中的整数进行乘法运算。
Stream
Optional
if(intOptional.isPresent())System.out.println(”乘法 =” + intOptional.get()); //120
流计数()示例:我们可以使用这个终端操作来计算流中的项目数。
Stream
System.out.println(”流中的元素数=” + numbers1.count()); //5
流forEach()示例:这可以用于迭代流。我们可以将其用于打印流的所有元素。
Stream
numbers2.forEach(i – > System.out.print(i +“,”)); //1,2,3,4,5,
流匹配()示例:让我们看一些Stream API中匹配方法的示例。
Stream
System.out.println(”流中包含4吗?”+ numbers3.anyMatch(i – > i == 4));
//流中包含4吗? 真的
Stream
System.out.println(”流中包含所有小于10的元素吗?”+ numbers4.allMatch(i – > i <10));
//流中包含所有小于10的元素吗?真的
Stream
System.out.println(”流中不包含10吗?”+ numbers5.noneMatch(i – > i == 10));
//流不包含10吗?真的
findFirst()示例:这是一个短路终端操作,让我们看看如何使用它从以D开头的流中找到第一个字符串。
Stream
Optional
if(firstNameWithD.isPresent()){
System.out.println(”以D开头的名字=”+ firstNameWithD.get()); //大卫
}
Java 8 流API的限制
Java 8 Stream API 带来了许多新的功能,可用于处理列表和数组,但它也有一些限制。
- 无状态的lambda表达式:如果你正在使用并行流,而lambda表达式是有状态的,可能会导致随机的响应。我们来看一个简单的程序。StatefulParallelStream.java
“`
package com.Olivia.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
List
Stream
stream.map(s -> {
synchronized (result) {
if (result.size() < 10) {
result.add(s);
}
}
return s;
}).forEach( e -> {});
System.out.println(result);
}
}
“`
如果我们运行上述程序,会得到不同的结果,因为它取决于流的迭代方式,并且对于并行处理,我们没有定义任何顺序。如果我们使用顺序流,则不会出现这个问题。
一旦流被消耗,就不能再次使用。如您在上述示例中看到的,每次我都在创建一个流。
在Stream API中有很多方法,最令人困惑的部分是方法的重载。这使得学习曲线变得很漫长。
这就是关于Java 8 Stream示例教程的全部内容。我期待着使用这个特性,并通过并行处理使代码变得更易读且性能更好。参考资料:Java Stream API文档。