简介
Java 8 的 Stream API 是一种新的抽象,它允许以声明式的方式处理数据。这种抽象具有多种优势,包括:代码简洁、可读性强、以及能够轻松地利用多核架构。下面是关于 Java 8 Stream 的一些详细解释
解释
创建 Stream
- 从集合创建:
Collection.stream()
、Collection.parallelStream()
- 从数组创建:
Arrays.stream(array)
- 直接创建:
Stream.of(val1, val2, val3)
- 空 Stream:
Stream.empty()
- 无限 Stream:
Stream.iterate
和Stream.generate
中间操作 (Intermediate Operations)
filter
: 过滤
我们通过一个简单的例子来展示使用filter
和不使用filter
的区别。
import java.util.*;
import java.util.stream.*;
public class FilterExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用filter过滤出偶数
List<Integer> evensWithFilter = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println(evensWithFilter); // 输出: [2, 4, 6, 8, 10]
// 不使用filter,通过传统循环过滤出偶数
List<Integer> evensWithoutFilter = new ArrayList<>();
for (Integer n : numbers) {
if (n % 2 == 0) {
evensWithoutFilter.add(n);
}
}
System.out.println(evensWithoutFilter); // 输出: [2, 4, 6, 8, 10]
}
}
优点对比:
-
代码简洁性:
- 使用
filter
可以使代码更简洁、更易读。通过链式调用,我们可以在一行代码中完成过滤操作。 - 不使用
filter
则需要多行代码,并且需要明确的循环和条件判断。
- 使用
-
函数式风格:
filter
方法允许我们采用函数式编程风格,这种风格可以减少错误并提高代码的可维护性。- 传统的循环方式则是命令式编程风格,可能会引入更多错误,特别是在复杂的过滤逻辑中。
-
不变性:
- 使用
filter
方法不会修改原始的数据源,而是创建一个新的Stream。这种不变性是函数式编程的重要特点,有助于减少bug和侧效应。 - 传统的循环方式则可能会修改原始的数据,增加了出错的可能性。
- 使用
-
优化性能:
filter
操作是惰性的,只有在需要时才会执行。而且它可以与其他Stream操作如map
、sorted
等一起优化,提高性能。- 传统的循环则是立即执行的,可能会导致不必要的计算,特别是在大数据集上。
map
: 映射,将每个元素映射到另一个对象上。
让我们通过一个示例来比较使用 map
和不使用 map
的效果:
import java.util.*;
import java.util.stream.*;
public class MapExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 使用 map 将每个单词转换为大写
List<String> uppercasedWordsWithMap = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(uppercasedWordsWithMap); // 输出: [APPLE, BANANA, CHERRY]
// 不使用 map,通过传统循环将每个单词转换为大写
List<String> uppercasedWordsWithoutMap = new ArrayList<>();
for (String word : words) {
uppercasedWordsWithoutMap.add(word.toUpperCase());
}
System.out.println(uppercasedWordsWithoutMap); // 输出: [APPLE, BANANA, CHERRY]
}
}
优点对比:
-
代码简洁性:
- 使用
map
可以在一行代码中完成映射操作,使代码更为简洁、易读。 - 不使用
map
则需要多行代码,并且需要明确的循环和转换操作。
- 使用
-
函数式风格:
map
方法允许我们采用函数式编程风格,这种风格可以减少错误并提高代码的可维护性。- 传统的循环方式是命令式编程风格,可能会引入更多错误,特别是在复杂的映射逻辑中。
-
不变性:
- 使用
map
方法不会修改原始的数据源,而是创建一个新的 Stream。这种不变性是函数式编程的重要特点,有助于减少 bug 和侧效应。 - 传统的循环方式则可能会修改原始的数据,增加了出错的可能性。
- 使用
-
链式调用:
map
方法可以与其他 Stream 操作如filter
、sorted
等链式调用,提供了高度的灵活性和可组合性,使得代码更为模块化。- 传统的循环则难以与其他操作组合,可能需要额外的代码来实现同样的功能。
flatMap
: 扁平映射,可以将多个 Stream 合并为一个 Stream。
我们通过一个示例来比较使用flatMap
和不使用flatMap
的效果:
import java.util.*;
import java.util.stream.*;
public class FlatMapExample {
public static void main(String[] args) {
List<List<Integer>> listOfLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
// 使用flatMap将多个List合并成一个List
List<Integer> flatListWithFlatMap = listOfLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println(flatListWithFlatMap); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
// 不使用flatMap,通过传统循环将多个List合并成一个List
List<Integer> flatListWithoutFlatMap = new ArrayList<>();
for (List<Integer> list : listOfLists) {
flatListWithoutFlatMap.addAll(list);
}
System.out.println(flatListWithoutFlatMap); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
优点对比:
-
代码简洁性:
- 使用
flatMap
可以在一行代码中完成多个集合的合并,使代码更为简洁、易读。 - 不使用
flatMap
则需要多行代码,并且需要明确的循环和合并操作。
- 使用
-
函数式风格:
flatMap
方法允许我们采用函数式编程风格,有助于减少错误并提高代码的可维护性。- 传统的循环方式是命令式编程风格,可能会引入更多错误。
-
不变性:
- 使用
flatMap
方法不会修改原始的数据源,而是创建一个新的 Stream。这种不变性是函数式编程的重要特点。 - 传统的循环方式可能会修改原始的数据,增加了出错的可能性。
- 使用
-
链式调用:
flatMap
方法可以与其他 Stream 操作如filter
、map
等链式调用,提供了高度的灵活性和可组合性,使得代码更为模块化。- 传统的循环方式难以与其他操作组合,可能需要额外的代码来实现同样的功能。
distinct
: 去重,通过元素的 hashCode() 和 equals() 去除重复元素。
下面通过一个示例来展示使用 distinct
和不使用 distinct
的效果:
import java.util.*;
import java.util.stream.*;
public class DistinctExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4);
// 使用 distinct 去除重复元素
List<Integer> uniqueNumbersWithDistinct = numbers.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueNumbersWithDistinct); // 输出: [1, 2, 3, 4]
// 不使用 distinct,通过 HashSet 去除重复元素
Set<Integer> uniqueNumbersWithoutDistinct = new HashSet<>(numbers);
System.out.println(uniqueNumbersWithoutDistinct); // 输出: [1, 2, 3, 4]
}
}
优点对比:
-
代码简洁性:
- 使用
distinct
可以在一行代码中完成去重操作,使代码更为简洁、易读。 - 不使用
distinct
则需要创建一个新的 HashSet 对象,并将原始数据复制到该 HashSet 中进行去重。
- 使用
-
函数式风格:
distinct
方法允许我们以函数式的风格处理数据,减少错误并提高代码的可维护性。- 使用 HashSet 进行去重是一种命令式的编程风格,可能会引入更多错误。
-
不变性:
- 使用
distinct
方法不会修改原始的数据源,而是创建一个新的 Stream,保持了函数式编程的不变性特点。 - 使用 HashSet 进行去重可能会修改原始数据,增加了出错的可能性。
- 使用
-
链式调用:
distinct
方法可以与其他 Stream 操作如filter
、map
等链式调用,提供了高度的灵活性和可组合性,使得代码更为模块化。- 使用 HashSet 进行去重则难以与其他操作组合,可能需要额外的代码来实现同样的功能。
sorted
: 排序,可以提供一个 Comparator
进行自定义排序。
下面通过一个示例来展示使用 sorted
和不使用 sorted
的效果:
import java.util.*;
import java.util.stream.*;
public class SortedExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
// 使用 sorted 进行自然排序
List<String> sortedWordsWithSorted = words.stream()
.sorted()
.collect(Collectors.toList());
System.out.println(sortedWordsWithSorted); // 输出: [apple, banana, cherry, date]
// 不使用 sorted,通过 Collections.sort 进行排序
List<String> sortedWordsWithoutSorted = new ArrayList<>(words);
Collections.sort(sortedWordsWithoutSorted);
System.out.println(sortedWordsWithoutSorted); // 输出: [apple, banana, cherry, date]
}
}
优点对比:
-
代码简洁性:
- 使用
sorted
可以在一行代码中完成排序操作,使代码更为简洁、易读。 - 不使用
sorted
则需要复制原始数据到一个新的列表中,并使用Collections.sort
进行排序。
- 使用
-
函数式风格:
sorted
方法允许我们以函数式的风格处理数据,减少错误并提高代码的可维护性。- 使用
Collections.sort
进行排序是一种命令式的编程风格,可能会引入更多错误。
-
不变性:
- 使用
sorted
方法不会修改原始的数据源,而是创建一个新的 Stream,保持了函数式编程的不变性特点。 - 使用
Collections.sort
进行排序会修改原始数据,增加了出错的可能性。
- 使用
-
链式调用:
sorted
方法可以与其他 Stream 操作如filter
、map
等链式调用,提供了高度的灵活性和可组合性,使得代码更为模块化。- 使用
Collections.sort
进行排序则难以与其他操作组合,可能需要额外的代码来实现同样的功能。
peek
: 用于 debug,每个元素经过这个操作时都会执行给定的消费函数。
让我们通过一个示例来展示使用 peek
和不使用 peek
的效果:
import java.util.*;
import java.util.stream.*;
public class PeekExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 使用 peek 查看流中的元素
List<String> uppercasedWordsWithPeek = words.stream()
.peek(System.out::println)
.map(String::toUpperCase)
.collect(Collectors.toList());
// 输出:
// apple
// banana
// cherry
// 不使用 peek,直接进行映射操作
List<String> uppercasedWordsWithoutPeek = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// 无输出
}
}
优点对比:
-
调试方便:
- 使用
peek
可以在不改变流的情况下查看流中的元素,对于调试非常方便。 - 不使用
peek
则无法在流操作过程中查看流中的元素,可能需要额外的代码来进行调试。
- 使用
-
代码简洁性:
peek
提供了一种简洁的方式来查看和调试流操作。- 不使用
peek
可能需要额外的代码来查看流中的元素,可能会使代码变得更加复杂。
-
函数式风格:
peek
方法允许我们以函数式的风格查看流中的元素,使代码保持了函数式的风格。- 不使用
peek
可能需要使用命令式的代码来查看流中的元素,可能会打破函数式编程的风格。
-
链式调用:
peek
方法可以与其他 Stream 操作如filter
、map
等链式调用,提供了高度的灵活性和可组合性,使得代码更为模块化。- 不使用
peek
可能需要断开链式调用来查看流中的元素,可能会使代码变得更加复杂。
limit
和 skip
: 用于缩减 stream 的大小。
让我们通过一个示例来展示使用 limit
和 skip
的效果:
import java.util.*;
import java.util.stream.*;
public class LimitSkipExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用 limit 缩减流的大小
List<Integer> limitedNumbers = numbers.stream()
.limit(5)
.collect(Collectors.toList());
System.out.println(limitedNumbers); // 输出: [1, 2, 3, 4, 5]
// 使用 skip 跳过流中的元素
List<Integer> skippedNumbers = numbers.stream()
.skip(5)
.collect(Collectors.toList());
System.out.println(skippedNumbers); // 输出: [6, 7, 8, 9, 10]
}
}
优点解析:
-
代码简洁性:
limit
和skip
提供了简洁的方式来缩减或跳过流中的元素,无需额外的循环或条件判断。- 传统的方式可能需要使用循环和条件判断来实现相同的功能,代码可能会更加冗长。
-
函数式风格:
limit
和skip
允许我们以函数式的风格处理数据,使代码更加易读和可维护。- 传统的方式是命令式的,可能会引入更多错误,并降低代码的可读性。
-
不变性:
limit
和skip
不会修改原始数据源,而是创建一个新的 Stream,保持了函数式编程的不变性特点。- 传统的方式可能会修改原始数据,增加了出错的可能性。
-
链式调用:
limit
和skip
可以与其他 Stream 操作如filter
、map
等链式调用,提供了高度的灵活性和可组合性。- 传统的方式可能需要断开链式调用来实现相同的功能,可能会使代码变得更加复杂。
终止操作 (Terminal Operations)
forEach
: 对每个元素执行操作
让我们通过一个示例来展示使用 forEach
和不使用 forEach
的效果:
import java.util.*;
import java.util.stream.*;
public class ForEachExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 使用 forEach 输出每个单词
words.stream().forEach(System.out::println);
// 输出:
// apple
// banana
// cherry
// 不使用 forEach,通过传统循环输出每个单词
for (String word : words) {
System.out.println(word);
}
// 输出:
// apple
// banana
// cherry
}
}
优点对比:
-
代码简洁性:
- 使用
forEach
可以在一行代码中完成对每个元素的操作,使代码更为简洁、易读。 - 不使用
forEach
则需要明确的循环结构,代码可能会显得更加冗长。
- 使用
-
函数式风格:
forEach
方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。- 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
-
方法引用:
forEach
可以与方法引用一起使用,提供了一种简洁的方式来执行操作。- 传统的循环则难以与方法引用结合使用,可能需要额外的代码来实现相同的功能。
-
链式调用:
- 虽然
forEach
是一个终端操作,但它可以在其他流操作后进行链式调用,提供了高度的灵活性和可组合性。 - 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。
- 虽然
toArray
: 将 Stream 转换为数组
让我们通过一个示例来展示使用 toArray
和不使用 toArray
的效果:
import java.util.*;
import java.util.stream.*;
public class ToArrayExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 使用 toArray 将流转换为数组
String[] arrayWithToArray = words.stream().toArray(String[]::new);
System.out.println(Arrays.toString(arrayWithToArray)); // 输出: [apple, banana, cherry]
// 不使用 toArray,通过循环将流转换为数组
String[] arrayWithoutToArray = new String[words.size()];
for (int i = 0; i < words.size(); i++) {
arrayWithoutToArray[i] = words.get(i);
}
System.out.println(Arrays.toString(arrayWithoutToArray)); // 输出: [apple, banana, cherry]
}
}
优点对比:
-
代码简洁性:
- 使用
toArray
可以简洁地将流转换为数组,无需额外的循环或数组操作。 - 不使用
toArray
则需要显式的循环和数组赋值,代码可能会显得更加冗长。
- 使用
-
函数式风格:
toArray
方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。- 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
-
类型安全:
toArray
提供了一种类型安全的方式来创建数组,我们可以提供一个生成器函数来创建指定类型的数组。- 传统的循环方式也可以创建类型安全的数组,但可能需要更多的代码来实现。
-
链式调用:
toArray
是一个终端操作,但它可以在其他流操作后进行链式调用,提供了高度的灵活性和可组合性。- 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。
reduce
: 将 Stream 缩减成单个值
让我们通过一个示例来展示使用 reduce
和不使用 reduce
的效果:
import java.util.*;
import java.util.stream.*;
public class ReduceExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用 reduce 求和
Optional<Integer> sumWithReduce = numbers.stream()
.reduce(Integer::sum);
sumWithReduce.ifPresent(sum -> System.out.println("Sum with reduce: " + sum));
// 输出: Sum with reduce: 15
// 不使用 reduce,通过循环求和
int sumWithoutReduce = 0;
for (int number : numbers) {
sumWithoutReduce += number;
}
System.out.println("Sum without reduce: " + sumWithoutReduce);
// 输出: Sum without reduce: 15
}
}
优点对比:
-
代码简洁性:
- 使用
reduce
可以在一行代码中完成聚合操作,使代码更为简洁、易读。 - 不使用
reduce
则需要明确的循环结构和累加操作,代码可能会显得更加冗长。
- 使用
-
函数式风格:
reduce
方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。- 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
-
不变性:
reduce
是一个纯函数,不会修改流中的元素或产生任何副作用,符合函数式编程的不变性原则。- 传统的循环方式可能会引入变量的副作用,增加了出错的可能性。
-
链式调用:
reduce
是一个终端操作,但它可以在其他流操作后进行链式调用,提供了高度的灵活性和可组合性。- 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。
collect
: 将 Stream 转换成其他形式,如 List、Set 或 Map
让我们通过一个示例来展示使用 collect
和不使用 collect
的效果:
import java.util.*;
import java.util.stream.*;
public class CollectExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 使用 collect 将流元素收集到 List
List<String> listWithCollect = words.stream().collect(Collectors.toList());
System.out.println(listWithCollect); // 输出: [apple, banana, cherry]
// 不使用 collect,通过循环将流元素收集到 List
List<String> listWithoutCollect = new ArrayList<>();
for (String word : words) {
listWithoutCollect.add(word);
}
System.out.println(listWithoutCollect); // 输出: [apple, banana, cherry]
}
}
优点对比:
-
代码简洁性:
- 使用
collect
可以在一行代码中将流元素收集到集合,使代码更为简洁、易读。 - 不使用
collect
则需要明确的循环结构和集合操作,代码可能会显得更加冗长。
- 使用
-
函数式风格:
collect
方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。- 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
-
灵活性:
collect
方法提供了高度的灵活性,我们可以使用不同的Collector
实现来收集数据到不同类型的集合。- 传统的循环方式可能需要更多的代码来实现不同类型的集合的创建和填充。
-
链式调用:
collect
是一个终端操作,但它可以在其他流操作后进行链式调用,提供了高度的灵活性和可组合性。- 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。
anyMatch
, allMatch
, noneMatch
: 返回一个 boolean 值,表示 Stream 中的元素是否满足给定条件
下面是这三个方法的简单描述和示例:
anyMatch:
anyMatch
方法测试流中的任何元素是否满足指定的条件。-
import java.util.*; import java.util.stream.*; public class MatchExample { public static void main(String[] args) { List<String> words = Arrays.asList("apple", "banana", "cherry"); // 使用 anyMatch 测试是否有单词以 "a" 开头 boolean anyMatch = words.stream().anyMatch(word -> word.startsWith("a")); System.out.println(anyMatch); // 输出: true } }
allMatch:
allMatch
方法测试流中的所有元素是否满足指定的条件。-
// 使用 allMatch 测试是否所有单词的长度大于 4 boolean allMatch = words.stream().allMatch(word -> word.length() > 4); System.out.println(allMatch); // 输出: false
noneMatch:
noneMatch
方法测试流中的所有元素是否都不满足指定的条件。-
// 使用 noneMatch 测试是否没有单词以 "z" 开头 boolean noneMatch = words.stream().noneMatch(word -> word.startsWith("z")); System.out.println(noneMatch); // 输出: true
优点对比(以 anyMatch
为例,与传统循环对比):
-
代码简洁性:
- 使用
anyMatch
可以在一行代码中完成条件测试,使代码更为简洁、易读。 - 传统的循环方式可能需要明确的循环结构和条件判断,代码可能会显得更加冗长。
- 使用
-
函数式风格:
anyMatch
、allMatch
和noneMatch
方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。- 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
-
链式调用:
- 这些方法可以与其他流操作链式调用,提供了高度的灵活性和可组合性。
- 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。
findFirst
, findAny
: 返回 Stream 中的第一个或任意元素
findFirst
和 findAny
是 Java 8 Stream API 中的终端操作,它们用于从流中检索元素。这两个方法返回一个 Optional
对象,该对象可能包含找到的元素,或者如果流为空或没有元素满足条件,则为 Optional.empty
。
findFirst:
findFirst
方法返回流中的第一个元素。它常用于有序流,其中元素的顺序有意义。-
import java.util.*; import java.util.stream.*; public class FindExample { public static void main(String[] args) { List<String> words = Arrays.asList("apple", "banana", "cherry"); // 使用 findFirst 获取第一个单词 Optional<String> firstWord = words.stream().findFirst(); firstWord.ifPresent(System.out::println); // 输出: apple } }
findAny:
findAny
方法返回流中的任何元素。在并行流处理时,它可能会返回任何合适的元素,使其对于并行处理更为友好。-
// 使用 findAny 获取任何一个单词 Optional<String> anyWord = words.stream().findAny(); anyWord.ifPresent(System.out::println); // 输出可能是: apple 或 banana 或 cherry
优点对比:
-
代码简洁性:
- 使用
findFirst
和findAny
可以在一行代码中获取流中的元素,使代码更为简洁、易读。 - 不使用这些方法可能需要明确的循环结构和条件判断,代码可能会显得更加冗长。
- 使用
-
函数式风格:
findFirst
和findAny
方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。- 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
-
并行友好:
findAny
是并行友好的,它可以在并行流处理时提供更好的性能。findFirst
在并行流中可能会受到顺序保证的限制,性能可能不如findAny
。
-
Optional 处理:
findFirst
和findAny
返回一个Optional
对象,它提供了一种更安全、更现代的方式来处理可能的null
值。- 传统的方式可能需要手动检查
null
值,增加了出错的可能性。
count
: 返回 Stream 中的元素数量
让我们通过一个示例来展示使用 count
和不使用 count
的效果:
import java.util.*;
import java.util.stream.*;
public class CountExample {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "cherry");
// 使用 count 计算单词数量
long countWithCount = words.stream().count();
System.out.println(countWithCount); // 输出: 3
// 不使用 count,通过循环计算单词数量
int countWithoutCount = 0;
for (String word : words) {
countWithoutCount++;
}
System.out.println(countWithoutCount); // 输出: 3
}
}
优点对比:
-
代码简洁性:
- 使用
count
可以在一行代码中完成元素计数,使代码更为简洁、易读。 - 不使用
count
则需要明确的循环结构和计数变量,代码可能会显得更加冗长。
- 使用
-
函数式风格:
count
方法允许我们以函数式的风格处理数据,使代码更加易读和可维护。- 传统的循环方式是命令式的,可能会引入更多错误,并降低代码的可读性。
-
链式调用:
count
是一个终端操作,但它可以在其他流操作后进行链式调用,提供了高度的灵活性和可组合性。- 传统的循环可能需要断开链式调用来执行操作,可能会使代码变得更加复杂。
数值流 (Numeric Streams)
IntStream, LongStream, DoubleStream: 为基本数据类型提供的特殊 Stream,可以避免装箱和拆箱操作。
下面是它们的主要用途和实际应用场景:
-
处理基本数据类型:
- 当需要处理的数据是基本数据类型(
int
,long
,double
)时,使用这些特殊流可以提高效率和性能。
- 当需要处理的数据是基本数据类型(
-
数值范围生成:
IntStream
,LongStream
提供了range
和rangeClosed
方法来生成数值范围内的元素,可以用于迭代或循环的替代。
// 生成 1 到 10 的整数序列
IntStream.range(1, 11).forEach(System.out::println);
数值统计:
- 这些特殊流提供了如
sum
,average
,max
,min
等方法来进行基本的数值统计。
IntStream numbers = IntStream.of(1, 2, 3, 4, 5);
System.out.println(numbers.sum()); // 输出: 15
映射和过滤:
- 与常规流一样,这些特殊流也提供了
map
,filter
等方法来进行元素的映射和过滤。
IntStream numbers = IntStream.of(1, 2, 3, 4, 5);
IntStream evenNumbers = numbers.filter(n -> n % 2 == 0);
evenNumbers.forEach(System.out::println); // 输出: 2 4
并行处理:
- 通过
parallel
方法,这些特殊流可以进行并行处理,以提高大数据集处理的效率。
LongStream.rangeClosed(1, 1000000)
.parallel()
.sum();
实际应用场景:
- 在实际应用中,当我们需要处理大量的基本数据类型时,使用
IntStream
,LongStream
,DoubleStream
可以提高程序的性能和效率。例如,在数值计算、数据分析、统计和科学计算等领域,这些特殊流的使用非常普遍。
总的来说,IntStream
, LongStream
, 和 DoubleStream
提供了一种高效、简洁和函数式的方式来处理基本数据类型的集合,使得我们能够以更高的性能和更少的代码来完成相关的任务。
并行流 (Parallel Streams)
parallel
: 将一个顺序流转换为并行流,可以使得操作在多个线程上并发执行,提高执行效率。
import java.util.*;
import java.util.stream.*;
public class ParallelExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用 parallel 创建一个并行流,并计算数字的总和
long startTime = System.currentTimeMillis();
long sumWithParallel = numbers.stream().parallel().mapToInt(Integer::intValue).sum();
long endTime = System.currentTimeMillis();
System.out.println("Sum with parallel: " + sumWithParallel + ", Time taken: " + (endTime - startTime) + " ms");
// 不使用 parallel,创建一个顺序流,并计算数字的总和
startTime = System.currentTimeMillis();
long sumWithoutParallel = numbers.stream().mapToInt(Integer::intValue).sum();
endTime = System.currentTimeMillis();
System.out.println("Sum without parallel: " + sumWithoutParallel + ", Time taken: " + (endTime - startTime) + " ms");
}
}
优点对比:
-
性能提升:
- 使用
parallel
在多核处理器环境下通常可以显著提高代码的执行效率,因为它允许多个线程同时处理数据。 - 不使用
parallel
则只能在单个线程上顺序处理数据,效率可能较低。
- 使用
-
简洁性:
- 使用
parallel
可以简单地将顺序流转换为并行流,无需额外的代码或线程管理。 - 不使用
parallel
则可能需要手动创建和管理线程,代码可能会显得更为复杂。
- 使用
-
线程安全:
- 使用
parallel
时,需要注意线程安全问题,确保操作是线程安全的,以避免数据竞争或不一致的结果。 - 不使用
parallel
则无需考虑线程安全问题,因为所有操作都在单个线程上执行。
- 使用
-
任务分解:
parallel
会自动将任务分解为多个小任务,并在多个线程上并行执行。- 不使用
parallel
则所有任务都在单个线程上顺序执行,无法利用多核处理器的优势。