一、简介
1、什么是集合流式编程
集合流式编程(Stream API)是Java 8引入的一个功能强大的特性,它提供了一种更简洁、更高效的方式来操作集合数据。它的设计目标是让开发者能够以一种更声明式的风格来处理集合数据,减少了显式的迭代和条件判断,使代码更加清晰和易于理解。
集合流式编程的主要特点如下:
-
链式操作:集合流提供了一系列的方法,这些方法可以通过链式调用来进行操作。这种链式操作的方式使得代码更加简洁、易读,可以通过不同的方法组合出复杂的操作流程。
-
内部迭代:传统的集合操作需要显式地使用迭代器或循环来遍历集合元素,而集合流式编程使用内部迭代的方式来处理数据,这意味着我们只需要关注对数据的操作,而不需要关注具体的迭代过程。
-
惰性求值:集合流式编程中的操作分为中间操作和终端操作。中间操作是对流进行处理的过程,它们不会立即执行,而是在终端操作被调用时才会触发执行。这种惰性求值的机制可以优化性能,避免不必要的计算。
-
并行处理:集合流式编程提供了并行处理的能力,可以利用多核处理器的优势,对数据进行并行操作,提高处理速度。通过调用
parallel()
方法,可以将串行流转换为并行流,实现并行处理。
集合流式编程的使用可以极大地简化集合数据的处理代码,提高开发效率。以下是一个简单的示例,展示了集合流的使用:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.stream()
.filter(n -> n % 2 == 0) // 筛选偶数
.mapToInt(n -> n) // 转换为int类型
.sum(); // 求和
System.out.println(sum); // 输出:30
在上面的示例中,我们使用stream()
方法将List转换为流,然后通过filter()
方法筛选出偶数,再通过mapToInt()
方法将流中的元素转换为int类型,最后使用sum()
方法求和。整个过程通过链式调用完成,代码简洁明了。
除了上述示例中的操作方法,集合流还提供了丰富的操作,如映射(map()
)、排序(sorted()
)、去重(distinct()
)、分组(groupBy()
)等,可以根据具体需求进行选择和组合。
需要注意的是,
集合流是一次性使用的,一旦流被消费或终端操作执行完毕,就不能再次使用。如果需要对同一集合进行多次操作,可以使用Stream
的Supplier
来创建新的流。
总之,集合流式编程为Java开发者提供了一种更简洁、更高效的集合数据处理方式,它使得代码更具可读性,同时还提供了并行处理的能力,可以提高性能。
2、为什么要使用集合流式编程
使用集合流式编程有以下几个优点:
-
简洁和可读性更好:集合流式编程使用链式操作的方式,可以将多个操作组合在一起,形成一个流畅的操作链。这种链式调用的方式使得代码更加简洁、易读,可以更清晰地表达对集合数据的处理逻辑,减少了显式的迭代和条件判断。
-
代码可维护性更高:集合流式编程能够将数据处理的步骤和逻辑清晰地表达出来,使代码更易于维护和理解。每个操作都是独立的,可以按需添加、删除或修改操作,而不需要关心具体的迭代过程。这种模块化的设计使得代码更加灵活,易于扩展和修改。
-
提高开发效率:使用集合流式编程可以减少代码量,并且能够通过链式操作一次性完成多个处理步骤,避免了中间变量的定义和赋值。这种简洁的编码风格可以提高开发效率,减少冗余代码的编写,从而更快地实现业务需求。
-
可以实现更高效的并行处理:集合流式编程提供了并行处理的能力,可以利用多核处理器的优势,将数据分成多个部分并行处理,从而提高处理速度。通过调用
parallel()
方法,可以将串行流转换为并行流,实现并行处理。这在处理大规模数据集合时尤为重要,可以充分利用硬件资源,加快数据处理速度。 -
与函数式编程相结合:集合流式编程借鉴了函数式编程的思想,强调对数据的转换和操作,而不是对迭代过程的关注。它提供了丰富的函数式操作方法,如
map()
、filter()
、reduce()
等,使得代码更加抽象和灵活。函数式编程的特性,如不可变性、纯函数等,也有助于编写更可靠、可测试和可维护的代码。
3、集合流式编程的步骤
集合流式编程的步骤可以概括为以下几个:
-
创建流:首先,需要将要操作的集合转换为流。可以使用集合的
stream()
方法来创建一个顺序流(Sequential Stream)或parallelStream()
方法来创建一个并行流(Parallel Stream)。顺序流适用于单线程操作,而并行流适用于多线程并行操作。 -
中间操作:对创建的流进行中间操作。中间操作是一系列对流进行处理的操作,可以通过链式调用来组合多个中间操作。中间操作并不会立即执行,而是在终端操作被调用时才会触发执行。常见的中间操作包括筛选(
filter()
)、映射(map()
)、去重(distinct()
)、排序(sorted()
)等。 -
终端操作:对经过中间操作的流进行终端操作。终端操作是流操作的最后一步,会触发流的执行并产生结果。终端操作可以是获取结果(如
collect()
、toArray()
)、聚合计算(如count()
、sum()
、max()
、min()
)、遍历打印(如forEach()
)等。
需要注意的是,集合流式编程的中间操作和终端操作可以根据需求进行选择和组合。中间操作可以有多个,但终端操作只能有一个,并且终端操作是触发流执行的标志。
以下是一个示例,演示了集合流式编程的基本步骤:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.stream()
.filter(n -> n % 2 == 0) // 中间操作:筛选偶数
.mapToInt(n -> n) // 中间操作:转换为int类型
.sum(); // 终端操作:求和
System.out.println(sum); // 输出:30
在上面的示例中,我们首先使用stream()
方法将numbers
集合转换为顺序流,然后通过filter()
方法筛选出偶数,再通过mapToInt()
方法将流中的元素转换为int类型,最后使用sum()
方法求和。整个过程通过链式调用完成,代码简洁明了。
需要根据具体的需求选择合适的中间操作和终端操作,以实现预期的数据处理和结果输出。集合流式编程的灵活性和简洁性可以提高开发效率和代码可读性。
二、最终操作
将流中的数据整合到一起,可以存入一个集合,也可以直接对流中的数据进行遍历、数据统计.….,通过最终操作,需要掌握如何从流中提取出来我们想要的信息。注意事项:最终操作,之所以叫最终操作,是因为,在最终操作执行结束后,会关闭这个流,流中的所有数据都会销毁。如果使用一个已经关闭了的流,会出现异常。
1、最终操作collect
collect
是一个常用的终端操作,它将流中的元素收集到一个集合或其他数据结构中。collect
操作接受一个Collector
参数,该参数定义了如何收集元素和生成最终的结果。
下面是一个使用collect
操作的案例,演示了如何将字符串列表中的元素连接成一个字符串:
List<String> strings = Arrays.asList("Hello", "world", "!");
String result = strings.stream()
.collect(Collectors.joining(" "));
System.out.println(result); // 输出:Hello world !
在上面的例子中,我们使用collect
操作将字符串列表中的元素连接成一个字符串。我们通过Collectors.joining(" ")
指定了连接字符串的分隔符为空格。最终得到了"Hello world !"的结果。
除了字符串的连接,collect
操作还可以将元素收集到列表、集合、映射等数据结构中。下面是一个将整数流中的偶数收集到一个新的列表的示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 输出:[2, 4]
在上面的例子中,我们使用collect
操作将整数流中的偶数收集到一个新的列表中。通过Collectors.toList()
指定了将元素收集到列表中。最终得到了包含偶数[2, 4]的结果列表。
除了toList()
,Java还提供了许多其他的Collector
工具类,如toSet()
(将元素收集到集合)、toMap()
(将元素收集到映射)、toCollection()
(将元素收集到自定义集合)、summarizingInt()
(统计信息汇总)等,可以根据需求选择合适的Collector
来实现元素的收集。
collect
操作在集合流式编程中非常有用,它能够将流中的元素收集到不同的数据结构中,方便进行后续的处理和操作。通过使用不同的Collector
,我们可以实现各种灵活的元素收集需求。
2、最终操作reduce
reduce
是一个常用的终端操作,它通过对流中的元素进行逐个操作,并将操作的结果累积到一个最终的值上。它接受一个二元操作符作为参数,该操作符定义了如何将两个元素进行操作并生成一个新的值。
下面是一个使用reduce
操作的案例,演示了如何计算集合中所有元素的和:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println(sum); // 输出:15
在上面的例子中,我们使用reduce
操作计算了集合中所有元素的和。初始值为0,二元操作符(a, b) -> a + b
将前一个结果与当前元素相加得到新的结果,最终得到了总和15。
reduce
操作也可以与并行流一起使用,实现并行计算。下面是一个使用并行流和reduce
操作计算集合中所有元素的乘积的示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int product = numbers.parallelStream()
.reduce(1, (a, b) -> a * b);
System.out.println(product); // 输出:120
在上面的例子中,我们使用了并行流parallelStream()
,通过reduce
操作计算了集合中所有元素的乘积。初始值为1,二元操作符(a, b) -> a * b
将前一个结果与当前元素相乘得到新的结果,最终得到了乘积120。
需要注意的是,reduce
操作的初始值是可选的,如果流为空,那么初始值就是结果。此外,如果初始值为null
,则需要使用Optional
来处理结果。
reduce
操作在集合流式编程中非常有用,可以通过定义不同的二元操作符,实现对流中元素的累积操作,从而得到最终的结果。它的灵活性使得我们可以进行各种复杂的数据处理和计算。
3、最终操作count&forEach
count
和forEach
是常用的最终操作,用于对流中的元素进行计数和遍历操作。
count()
:count
方法用于统计流中的元素数量,并返回统计结果。
下面是一个使用count
操作的案例,统计列表中大于等于 5 的元素个数:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
long count = numbers.stream()
.filter(n -> n >= 5)
.count();
System.out.println(count); // 输出:6
在上述示例中,我们使用count
方法统计了列表中大于等于 5 的元素的个数。通过链式调用filter
方法筛选满足条件的元素,然后调用count
方法获取统计结果。
forEach(Consumer<? super T> action)
:forEach
方法用于遍历流中的每个元素,并对每个元素执行给定的操作。
下面是一个使用forEach
操作的案例,遍历集合中的字符串并打印每个字符串的长度:
List<String> strings = Arrays.asList("apple", "banana", "orange");
strings.stream()
.forEach(s -> System.out.println(s.length()));
在上述示例中,我们使用forEach
方法遍历了字符串列表,并对每个字符串打印其长度。通过传入一个 lambda 表达式来执行打印操作。
需要注意的是,forEach
操作是一个终端操作,它会遍历流中的每个元素并对其执行操作,但并不会返回结果。
count
和forEach
操作都是常见且常用的终端操作,分别用于统计元素数量和对每个元素执行操作。它们在集合流式编程中经常用于对数据进行统计、遍历和处理。
4、最终操作max&main
max
和min
是常用的最终操作,用于获取流中的最大值和最小值。
max(Comparator<? super T> comparator)
:max
方法返回流中的最大值。需要传入一个比较器来确定元素的顺序。
下面是一个使用max
操作的案例,获取整数流中的最大值:
List<Integer> numbers = Arrays.asList(1, 3, 5, 2, 4);
Optional<Integer> max = numbers.stream()
.max(Integer::compareTo);
if (max.isPresent()) {
System.out.println("Max value: " + max.get()); // 输出:Max value: 5
} else {
System.out.println("No maximum value found.");
}
在上述示例中,我们使用max
方法获取了整数流中的最大值。通过传入Integer::compareTo
作为比较器,确定了整数的顺序。使用Optional
来处理可能不存在最大值的情况。
min(Comparator<? super T> comparator)
:min
方法返回流中的最小值。同样需要传入一个比较器来确定元素的顺序。
下面是一个使用min
操作的案例,获取字符串流中的最小长度:
List<String> strings = Arrays.asList("apple", "banana", "orange");
Optional<String> min = strings.stream()
.min(Comparator.comparingInt(String::length));
if (min.isPresent()) {
System.out.println("Min length: " + min.get()); // 输出:Min length: apple
} else {
System.out.println("No minimum length found.");
}
在上述示例中,我们使用min
方法获取了字符串流中长度最小的字符串。通过传入Comparator.comparingInt(String::length)
作为比较器,确定了字符串的顺序。同样使用Optional
来处理可能不存在最小值的情况。
使用max
和min
方法时,需要注意流中的元素类型必须实现了Comparable
接口或者提供自定义的比较器。否则会抛出ClassCastException
。
max
和min
操作是非常有用的最终操作,它们能够快速获取流中的最大值和最小值。通过传入比较器,可以灵活地确定元素的顺序。在数据处理和查找最值的场景中,它们经常被使用。
5、最终操作matching
Matching 操作是一类常用的最终操作,用于检查流中的元素是否满足特定的条件。Java 提供了三个 Matching 操作:allMatch
、anyMatch
和 noneMatch
。
allMatch(Predicate<? super T> predicate)
:allMatch
方法用于检查流中的所有元素是否都满足给定的条件。
下面是一个使用 allMatch
操作的案例,检查整数流中的所有元素是否都是偶数:
List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);
boolean allEven = numbers.stream()
.allMatch(n -> n % 2 == 0);
System.out.println(allEven); // 输出:true
在上述示例中,我们使用 allMatch
方法检查整数流中的所有元素是否都是偶数。通过传入 n -> n % 2 == 0
作为断言条件,判断元素是否满足偶数的条件。最终得到 true
,说明所有元素都是偶数。
anyMatch(Predicate<? super T> predicate)
:anyMatch
方法用于检查流中的任意一个元素是否满足给定的条件。
下面是一个使用 anyMatch
操作的案例,检查字符串流中是否存在以 “a” 开头的元素:
List<String> strings = Arrays.asList("apple", "banana", "orange");
boolean anyStartsWithA = strings.stream()
.anyMatch(s -> s.startsWith("a"));
System.out.println(anyStartsWithA); // 输出:true
在上述示例中,我们使用 anyMatch
方法检查字符串流中是否存在以 “a” 开头的元素。通过传入 s -> s.startsWith("a")
作为断言条件,判断元素是否以 “a” 开头。最终得到 true
,说明存在符合条件的元素。
noneMatch(Predicate<? super T> predicate)
:noneMatch
方法用于检查流中的所有元素是否都不满足给定的条件。
下面是一个使用 noneMatch
操作的案例,检查整数流中是否不存在负数元素:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean noneNegative = numbers.stream()
.noneMatch(n -> n < 0);
System.out.println(noneNegative); // 输出:true
在上述示例中,我们使用 noneMatch
方法检查整数流中是否不存在负数元素。通过传入 n -> n < 0
作为断言条件,判断元素是否小于 0。最终得到 true
,说明不存在负数元素。
Matching 操作允许我们快速检查流中的元素是否满足给定的条件。根据不同的需求,我们可以选择使用 allMatch
、anyMatch
或 noneMatch
方法来进行相应的判断。
6、最终操作find
find
是一类常用的最终操作,用于从流中查找满足特定条件的元素,并返回一个 Optional
对象。
Java 提供了两种 find
操作:findFirst
和 findAny
。
findFirst()
:findFirst
方法用于从流中查找第一个满足条件的元素。
下面是一个使用 findFirst
操作的案例,查找整数流中第一个偶数:
List<Integer> numbers = Arrays.asList(1, 3, 4, 2, 5);
Optional<Integer> firstEven = numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst();
if (firstEven.isPresent()) {
System.out.println("First even number: " + firstEven.get()); // 输出:First even number: 4
} else {
System.out.println("No even number found.");
}
在上述示例中,我们使用 findFirst
方法查找整数流中第一个偶数。通过链式调用 filter
方法筛选出满足条件的偶数,然后调用 findFirst
方法获取第一个偶数。使用 Optional
来处理可能不存在满足条件的情况。
findAny()
:findAny
方法用于从流中查找任意一个满足条件的元素。
下面是一个使用 findAny
操作的案例,查找字符串流中以 “a” 开头的任意一个元素:
List<String> strings = Arrays.asList("apple", "banana", "orange");
Optional<String> anyStartsWithA = strings.stream()
.filter(s -> s.startsWith("a"))
.findAny();
if (anyStartsWithA.isPresent()) {
System.out.println("Element starts with 'a': " + anyStartsWithA.get()); // 输出:Element starts with 'a': apple
} else {
System.out.println("No element starts with 'a' found.");
}
在上述示例中,我们使用 findAny
方法查找字符串流中以 “a” 开头的任意一个元素。通过链式调用 filter
方法筛选出满足条件的元素,然后调用 findAny
方法获取任意一个元素。同样使用 Optional
来处理可能不存在满足条件的情况。
findFirst
和findAny
操作都可以用于查找流中的元素,但findAny
通常在并行流中使用更高效。它们返回的是一个Optional
对象,因此需要进行相应的处理。
7、最终操作IntStream
IntStream
是一个特殊的流,用于处理基本类型 int
的元素。它提供了一系列最终操作,用于对 IntStream
进行终端操作。
下面介绍几个常用的 IntStream
最终操作,并提供相应的案例:
sum()
:sum
方法用于计算IntStream
中所有元素的和。
IntStream numbers = IntStream.of(1, 2, 3, 4, 5);
int sum = numbers.sum();
System.out.println(sum); // 输出:15
在上述示例中,我们创建了一个 IntStream
包含整数 1 到 5,然后调用 sum
方法计算它们的和。
average()
:average
方法用于计算IntStream
中所有元素的平均值。
IntStream numbers = IntStream.of(1, 2, 3, 4, 5);
OptionalDouble average = numbers.average();
if (average.isPresent()) {
System.out.println(average.getAsDouble()); // 输出:3.0
} else {
System.out.println("No average value found.");
}
在上述示例中,我们创建了一个 IntStream
包含整数 1 到 5,然后调用 average
方法计算它们的平均值。使用 OptionalDouble
处理可能不存在平均值的情况。
min()
和max()
:min
和max
方法分别用于查找IntStream
中的最小值和最大值。
IntStream numbers = IntStream.of(5, 2, 8, 3, 1);
OptionalInt min = numbers.min();
OptionalInt max = numbers.max();
if (min.isPresent()) {
System.out.println("Min value: " + min.getAsInt()); // 输出:Min value: 1
} else {
System.out.println("No minimum value found.");
}
if (max.isPresent()) {
System.out.println("Max value: " + max.getAsInt()); // 输出:Max value: 8
} else {
System.out.println("No maximum value found.");
}
在上述示例中,我们创建了一个 IntStream
包含整数 5、2、8、3、1,然后分别调用 min
和 max
方法查找最小值和最大值。使用 OptionalInt
处理可能不存在最小值和最大值的情况。
IntStream
还提供了其他一些最终操作,如 count()
用于计数元素的个数,forEach()
用于遍历元素并执行操作等。
IntStream
的最终操作可以让我们方便地对整数流进行统计、计算和查找等操作。根据具体需求,选择相应的最终操作来处理 IntStream
的元素。
三、中间操作
1、filter
中间操作 filter
是流操作中常用的一种操作,它用于筛选流中满足特定条件的元素,并生成一个新的流。
filter
接受一个 Predicate
(谓词)作为参数,用于确定是否保留流中的元素。只有满足谓词条件的元素才会被保留在新的流中。
下面是一个关于 filter
操作的示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evenNumbers); // 输出:[2, 4, 6, 8, 10]
在上述示例中,我们有一个包含整数的列表 numbers
,然后使用流操作将其转换为流。接着使用 filter
方法筛选出所有偶数,并最终使用 collect
方法将结果收集为一个新的列表。
在 filter
方法的参数中,我们使用了一个 lambda 表达式 n -> n % 2 == 0
。这个 lambda 表达式代表了一个谓词,用于判断元素是否为偶数。只有满足谓词条件的元素才会被保留在新的流中。
2、distinct
中间操作 distinct
是流操作中常用的一种操作,它用于去除流中的重复元素,并生成一个新的流。
distinct
操作会根据元素的 hashCode
和 equals
方法来判断元素是否重复。只有在流中第一次出现的元素会被保留,后续出现的重复元素会被过滤掉。
下面是一个关于 distinct
操作的示例:
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 4, 5, 5);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctNumbers); // 输出:[1, 2, 3, 4, 5]
在上述示例中,我们有一个包含整数的列表 numbers
,然后使用流操作将其转换为流。接着使用 distinct
方法去除重复元素,并最终使用 collect
方法将结果收集为一个新的列表。
通过 distinct
操作,我们可以快速去除流中的重复元素,确保每个元素都是唯一的。这在需要对数据进行去重的情况下非常有用。
需要注意的是,
distinct
操作依赖于元素的hashCode
和equals
方法。因此,要确保流中的元素正确实现了这两个方法,以便正确判断元素是否重复。
另外,如果流中的元素是自定义对象,需要确保正确重写了 hashCode
和 equals
方法,以便正确进行去重操作。
3、sorted
中间操作 sorted
是流操作中常用的一种操作,它用于对流中的元素进行排序,并生成一个新的流。
sorted
操作可以用于对元素进行升序或降序排序。默认情况下,它会按照元素的自然顺序进行排序,或者根据指定的比较器进行排序。
下面是一些关于 sorted
操作的示例:
- 对整数流进行升序排序:
List<Integer> numbers = Arrays.asList(5, 3, 8, 2, 1, 4);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedNumbers); // 输出:[1, 2, 3, 4, 5, 8]
在上述示例中,我们有一个包含整数的列表 numbers
,然后使用流操作将其转换为流。接着使用 sorted
方法对流中的元素进行升序排序,并最终使用 collect
方法将结果收集为一个新的列表。
- 对字符串流进行降序排序:
List<String> strings = Arrays.asList("apple", "banana", "orange", "grape");
List<String> sortedStrings = strings.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(sortedStrings); // 输出:[orange, grape, banana, apple]
在上述示例中,我们有一个包含字符串的列表 strings
,然后使用流操作将其转换为流。接着使用 sorted
方法并传入 Comparator.reverseOrder()
来对流中的元素进行降序排序,并最终使用 collect
方法将结果收集为一个新的列表。
通过 sorted
操作,我们可以对流中的元素进行排序,以满足特定的排序需求。可以使用默认的自然排序,也可以提供自定义的比较器来进行排序。
需要注意的是,sorted
操作返回一个新的流,而不是对原始流进行排序。这样可以保持流的不可变性,并且在需要时可以进一步对排序后的流进行其他操作。
4、中间操作limit&skip
中间操作 limit
和 skip
是流操作中常用的一对操作,它们用于对流进行截取或跳过操作,生成一个新的流。
limit
:limit
方法用于截取流中的前 N 个元素,生成一个新的流。
下面是一个关于 limit
操作的示例,截取整数流的前 3 个元素:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> limitedNumbers = numbers.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(limitedNumbers); // 输出:[1, 2, 3]
在上述示例中,我们有一个包含整数的列表 numbers
,然后使用流操作将其转换为流。接着使用 limit
方法截取前 3 个元素,并最终使用 collect
方法将结果收集为一个新的列表。
skip
:skip
方法用于跳过流中的前 N 个元素,生成一个新的流。
下面是一个关于 skip
操作的示例,跳过整数流的前 2 个元素:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> skippedNumbers = numbers.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println(skippedNumbers); // 输出:[3, 4, 5, 6]
在上述示例中,我们有一个包含整数的列表 numbers
,然后使用流操作将其转换为流。接着使用 skip
方法跳过前 2 个元素,并最终使用 collect
方法将结果收集为一个新的列表。
通过使用
limit
和skip
操作,我们可以灵活地对流进行截取或跳过操作,根据需求获取所需的部分元素。这在处理大量数据时非常有用,可以提高效率并节省资源。
5、中间操作map
中间操作 map
是流操作中常用的一种操作,它用于将流中的每个元素映射为另一个元素,并生成一个新的流。
map
接受一个函数作为参数,该函数定义了元素的映射规则。流中的每个元素都会被传递给该函数,并根据函数的映射规则生成一个新的元素。
下面是一个关于 map
操作的示例:
List<String> names = Arrays.asList("John", "Sarah", "Tom", "Alice");
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(nameLengths); // 输出:[4, 5, 3, 5]
在上述示例中,我们有一个包含字符串的列表 names
,然后使用流操作将其转换为流。接着使用 map
方法并传入 String::length
函数作为映射规则,将每个字符串映射为其长度,并最终使用 collect
方法将结果收集为一个新的列表。
通过 map
操作,我们可以对流中的每个元素应用一个函数,将其转换为另一种形式或提取出特定的属性。这在数据转换、数据提取和对象转换等场景下非常有用。
注意:
map
操作不会改变流的类型,它只是对流中的元素进行转换。如果需要改变流的类型,可以使用其他的流操作,如mapToInt
、mapToDouble
、mapToLong
等。这些操作可以将流转换为特定类型的流,以便进行进一步的操作和计算。
6、flatMap
中间操作 flatMap
是流操作中常用的一种操作,它用于将流中的每个元素映射为多个元素,并将这些元素扁平化为一个新的流。
flatMap
接受一个函数作为参数,该函数定义了元素的映射规则,并返回一个流。流中的每个元素都会被传递给该函数,并将其映射为一个流。最后,所有映射后的流会被扁平化为一个新的流。
下面是一个关于 flatMap
操作的示例:
List<List<Integer>> numbers = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
List<Integer> flattenedNumbers = numbers.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(flattenedNumbers); // 输出:[1, 2, 3, 4, 5, 6]
在上述示例中,我们有一个包含列表的列表 numbers
,其中每个内部列表表示一组整数。然后使用流操作将其转换为流。接着使用 flatMap
方法并传入 List::stream
函数作为映射规则,将每个内部列表映射为一个流,并最终将所有流扁平化为一个新的流。最后使用 collect
方法将结果收集为一个新的列表。
通过 flatMap
操作,我们可以将嵌套的数据结构扁平化为一个简单的流。这在处理多层嵌套的数据、展开嵌套的集合或关联对象等场景下非常有用。
注意:
flatMap
操作返回的是一个扁平化后的流,而不是一个嵌套的流。这样可以使得流的元素更加平坦,方便后续的操作和处理。
四、Collectors工具类
1、概念
Collectors是一个工具类,里面封装了很多方法,可以很方便的获取到一个 Collector 接口的实现类对象,从而可以使用 collect()方法,对流中的数据,进行各种各样的处理、整合。
2、常用方法
方法 | 描述 | 示例 |
---|---|---|
toList() | 将流中的元素收集到一个列表中 | List numbers = stream.collect(Collectors.toList()); |
toSet() | 将流中的元素收集到一个集合中 | Set names = stream.collect(Collectors.toSet()); |
toMap() | 将流中的元素按照键-值的形式收集到一个Map中 | Map<String, Integer> map = stream.collect(Collectors.toMap(keyMapper, valueMapper)); |
joining() | 将流中的元素拼接成一个字符串 | String result = stream.collect(Collectors.joining()); |
averagingInt() | 计算流中整数元素的平均值 | double average = stream.collect(Collectors.averagingInt(i -> i)); |
summingInt() | 计算流中整数元素的总和 | int sum = stream.collect(Collectors.summingInt(i -> i)); |
maxBy() | 根据指定的比较器选择流中的最大元素 | Optional max = stream.collect(Collectors.maxBy(comparator)); |
minBy() | 根据指定的比较器选择流中的最小元素 | Optional min = stream.collect(Collectors.minBy(comparator)); |
groupingBy() | 根据指定的分类函数对流中的元素进行分组 | Map<String, List> groups = stream.collect(Collectors.groupingBy(classifier)); |
partitioningBy() | 根据指定的条件对流中的元素进行分区 | Map<Boolean, List> partitions = stream.collect(Collectors.partitioningBy(predicate)); |
counting() | 计算流中元素的个数 | long count = stream.collect(Collectors.counting()); |
mapping() | 对流中的元素进行映射和收集 | List result = stream.collect(Collectors.mapping(function, collector)); |
reducing() | 对流中的元素进行归约操作 | Optional result = stream.collect(Collectors.reducing(BinaryOperator)); |
3、Collectors
工具类案例
- 将流中的元素收集到一个列表中:
List<Integer> numbers = stream.collect(Collectors.toList());
- 将流中的元素收集到一个集合中:
Set<String> names = stream.collect(Collectors.toSet());
- 将流中的元素按照键-值的形式收集到一个Map中:
Map<String, Integer> map = stream.collect(Collectors.toMap(keyMapper, valueMapper));
- 将流中的元素拼接成一个字符串:
String result = stream.collect(Collectors.joining());
- 计算流中整数元素的平均值:
double average = stream.collect(Collectors.averagingInt(i -> i));
- 计算流中整数元素的总和:
int sum = stream.collect(Collectors.summingInt(i -> i));
- 根据指定的比较器选择流中的最大元素:
Optional<Integer> max = stream.collect(Collectors.maxBy(comparator));
- 根据指定的比较器选择流中的最小元素:
Optional<Integer> min = stream.collect(Collectors.minBy(comparator));
- 根据指定的分类函数对流中的元素进行分组:
Map<String, List<Integer>> groups = stream.collect(Collectors.groupingBy(classifier));
- 根据指定的条件对流中的元素进行分区:
Map<Boolean, List<Integer>> partitions = stream.collect(Collectors.partitioningBy(predicate));
- 计算流中元素的个数:
long count = stream.collect(Collectors.counting());
- 对流中的元素进行映射和收集:
List<Integer> result = stream.collect(Collectors.mapping(function, collector));
- 对流中的元素进行归约操作:
Optional<Integer> result = stream.collect(Collectors.reducing(BinaryOperator));
通过使用
Collectors
工具类,我们可以方便地对流中的元素进行各种收集和归约操作,以满足不同的需求。这些方法提供了一种简洁而强大的方式来处理流中的数据,并生成最终的结果。