Stream API
Java 8 引入的,这是一种用于处理集合、数组或其他数据源的高效、功能强大且易于使用的高级抽象。Stream API
提供了一种声明式编程模型,允许开发者以一种类似SQL
查询的方式对数据进行过滤
、映射
、聚合
等操作。
特性
Stream是数据流的抽象,它不是数据结构,不存储数据,而是从支持数据处理操作的源(如集合、数组、I/O通道等)生成一系列元素。Stream专注于对数据的计算,而不是数据的存储。
- 惰性求值(Lazy Evaluation) :Stream的许多操作(如filter、map等)都不会立即执行,而是等到真正需要结果时才进行计算,这称为惰性求值。这种机制有助于优化性能,特别是处理大数据集时,避免不必要的计算。
-
可并行处理(Parallel Processing) :Stream支持并行处理,可以在多核CPU上并行执行操作,提高计算效率。通过调用
parallel()
方法可以将串行流转换为并行流。 - 不可修改(Immutable) :Stream不支持修改操作,即不能添加、删除或替换其中的元素。这有助于避免数据竞争和同步问题,确保线程安全。
-
内部迭代(Internal Iteration) :传统集合遍历通常采用外部迭代(如for-each循环),而Stream采用内部迭代,由
Stream API
自身负责遍历数据源,开发者只需关注如何对每个元素进行操作。
Stream创建
Stream可以从各种数据源创建,如集合、数组、生成器函数等:
从集合或数组创建:
- 集合:通过
Collection
接口的stream()
方法。 - 数组:通过
Arrays.stream()
方法。
// 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> streamFromList = list.stream();
// 从数组创建
String[] array = {"d", "e", "f"};
Stream<String> streamFromArray = Arrays.stream(array);
创建无限流:
- 通过
Stream
类的静态方法,如iterate
、generate
和concat
。
// 无限流,生成一个无限序列的随机数
Stream<Double> randomStream = Stream.generate(Math::random);
// 无限流,生成一个无限序列的整数
Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 1);
创建空或单元素流:
- 使用
Stream.empty()
创建一个空的Stream
。 - 使用
Stream.of()
创建一个单元素的Stream
。
// 创建一个空的Stream流
Stream<String> emptyStream = Stream.empty();
// 创建一个但元素的Stream流
Stream<String> singleElementStream = Stream.of("Java");
通过 Stream.Builder创建:
- 对于需要构建复杂
Stream
的情况,可以使用Collectors.toCollection()
方法。
Stream.Builder<String> builder = Stream.builder();
builder.add("Java");
builder.add("Python");
builder.add("Scala");
Stream<String> streamFromBuilder = builder.build();
代码示例:
// 创建一个 List 集合,存储常见的变成语言名称
List<String> languages = Arrays.asList("Java", "Python", "Go", "PHP", "JavaScript");
// 过滤并映射 字符串开口为‘J’的元素
Stream<String> filteredStream = languages.stream()
// 过滤出以 "J" 开头的语言
.filter(language -> language.startsWith("J"))
// 转换为大写
.map(String::toUpperCase);
// 收集结果
List<String> uppercaseLanguages = filteredStream.collect(Collectors.toList());
// 遍历输出结果
uppercaseLanguages.forEach(System.out::println);// 输出 JAVA JAVASCRIPT
示例解析:
- 首先创建了一个
List
集合,然后通过调用stream()
方法创建了一个Stream
。 - 接着,我们使用
filter
方法过滤出以 "J" 开头的语言,然后使用map
方法将它们转换为大写。 - 最后,我们使用
collect
方法将结果收集到一个新的List
中,并打印出来。
注意事项:
-
避免在循环中创建 Stream:由于
Stream
是不可重复消费的,所以在循环中创建Stream
可能会导致意外的行为。 -
使用终端操作结束 Stream:在执行了一系列中间操作后,你需要调用一个终端操作来结束
Stream
并触发计算。
Stream操作
Stream API
提供了丰富的操作,可分为两大类:中间操作(Intermediate Operations)和终止操作(Terminal Operations)。
中间操作
中间操作
通常用于数据的筛选、转换、排序等操作,并且是惰性求值的,即它们不会立即执行,只有在调用终端操作时才会触发实际的计算。以下是Java Stream中一些常用的中间操作
筛选(filter
) :
- 通过
filter(Predicate p)
方法,根据给定的谓词(Predicate)函数,筛选出满足条件的元素。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// 筛选偶数
Stream<Integer> evenNumberStream = numbers.stream().filter(n -> n % 2 == 0);
// 收集结果并转成List集合
List<Integer> evenNumbers = evenNumberStream.collect(Collectors.toList());
System.out.println(evenNumbers);// 输出:[2, 4, 6, 8]
映射(map
) :
- 通过
map(Function<? super T, ? extends R> mapper)
方法,将Stream中的每个元素应用给定的函数,生成新的Stream,其中包含函数返回的结果。
List<String> words = Arrays.asList("Java", "Kotlin", "C#", "Python", "JavaScript");
// 获取每个元素的长度
Stream<Integer> wordLengthStream = words.stream().map(String::length);
// 收集结果
List<Integer> wordLengthList = wordLengthStream.collect(Collectors.toList());
System.out.println(wordLengthList);//输出:[4, 6, 2, 6, 10]
扁平化(flatMap
) :
- 通过
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
方法,将每个元素转换为一个Stream,然后将所有生成的Stream连接成一个单一的Stream。常用于处理嵌套数据结构。
List<List<String>> nestedWords = Arrays.asList(
Arrays.asList("apple", "banana"),
Arrays.asList("cherry", "date")
);
// 将两个Stream流合并成一个
Stream<String> flattenedWordsStream = nestedWords.stream()
.flatMap(List::stream);
// 收集结果
List<String> flattenedWords = flattenedWordsStream.collect(Collectors.toList());
System.out.println(flattenedWords); // 输出: [apple, banana, cherry, date]
去重(distinct
) :
- 通过
distinct()
方法,去除流中的重复元素。
List<String> words = Arrays.asList("Java", "Kotlin", "C#", "Python", "Java");
// 去除集合中重复的元素
Stream<String> uniqueStream = words.stream().distinct();
// 收集结果
List<String> uniqueWords = uniqueStream.collect(Collectors.toList());
System.out.println(uniqueWords);// 输出:[Java, Kotlin, C#, Python]
排序(sorted
)
- 通过
sorted(Comparator<? super T> comparator)
方法,自然排序(对于实现了Comparable
接口的元素类型)或指定比较器排序。
自然排序:
List<String> words = Arrays.asList("Java", "Kotlin", "C#", "Python","JavaScript", "PHP");
// 默认自然排序(按首字母排序)
Stream<String> sortedStream = words.stream().sorted();
// 收集结果
List<String> sortedWords = sortedStream.collect(Collectors.toList());
System.out.println(sortedWords); //输出:[C#, Java, JavaScript, Kotlin, PHP, Python]
自定义比较器排序:
List<String> words = Arrays.asList("Java", "Kotlin", "C#", "Python","JavaScript", "PHP");
// 自定义比较器(按长度)
Comparator<String> byLength = Comparator.comparingInt(String::length);
// 调用自定义比较器
Stream<String> sortedStream = words.stream().sorted(byLength);
// 收集结果
List<String> sortedWords = sortedStream.collect(Collectors.toList());
System.out.println(sortedWords);// 输出:[C#, PHP, Java, Kotlin, Python, JavaScript]
切片(Slicing
)
-
skip(long n)
:跳过前n个元素,返回剩余元素构成的新Stream。 -
limit(long maxSize)
:限制Stream的最大长度,保留前n个元素。 - 作用:对Stream进行分页或取部分子集。
List<String> words = Arrays.asList("Java", "Kotlin", "C#", "Python","JavaScript", "PHP");
// 跳到第三个开始输出(就是跳过开头两个)
Stream<String> skipStream = words.stream().skip(2);
List<String> skipWords = skipStream.collect(Collectors.toList());
System.out.println(skipWords);//输出:[C#, Python, JavaScript, PHP]
// 保留前两个元素
Stream<String> limitStream = words.stream().limit(2);
List<String> limitWords = limitStream.collect(Collectors.toList());
System.out.println(limitWords);//输出:[Java, Kotlin]
// 模拟分页输出
int pageSize = 3; // 每页个数
int currentPage = 2; // 第2页
long skipCount = (currentPage - 1) * pageSize; // 跳过前一页的元素数量
Stream<String> slicinStream = words.stream()
.skip(skipCount) // 有顺序要求,skip必须在前面
.limit(pageSize);
List<String> slicinWords = slicinStream.collect(Collectors.toList());
System.out.println(slicinWords);//输出:
注意事项:
- 中间操作可以链式调用,形成复杂的数据处理链。每个中间操作返回一个新的流,而原始流保持不变。这种链式操作使得
Stream API
非常灵活和强大。 - 中间操作是惰性求值的,它们不会立即执行,直到终端操作被调用。
- 中间操作可以复用流,即同一个流可以被多次使用,每次都会返回一个新的流。
- 过度使用中间操作可能会影响性能,因为它们会增加处理链的复杂性。
终端操作
Stream API
中的终端操作(Terminal Operations)是 Stream 操作链的结束,它们触发实际的计算并产生一个结果或副作用。终端操作可以是短路的(如 forEach
),也可以是非短路的(如 reduce
、collect
)。这些操作可以分为以下几个类别:
归约(Reduction) :归约操作将流中的所有元素合并为一个单一的值。常见的归约操作包括:
-
reduce(BinaryOperator<T>)
:使用给定的二元操作符将流元素累积起来,生成一个单一的结果。例如,计算所有元素的总和、求最大值或最小值等。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5,7,8,9);
// 集合中所有元素相加
Integer sum = numbers.stream().reduce(0, Integer::sum);
System.out.println(sum);//输出: 39
-
sum()
、max()
、min()
、average()
:针对数值型流的便捷归约操作,分别计算总和、最大值、最小值和平均值。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5,7,8,9);
double average = numbers.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(Double.NaN);
System.out.println(average);//输出: 4.875
解析:
-
mapToInt()
方法。此方法将当前的Stream<Integer>
转换为一个IntStream
,同时对流中的每个元素应用提供的函数。 -
Integer::intValue
表示将每个Integer
元素直接转换为其原始的int
值。 -
average()
方法,它是IntStream
提供的一个终端操作,用于计算流中所有整数的平均值。 -
orElse()
方法处理其可能的未包含值情况。 -
Double.NaN
,表示“非数字”(Not-a-Number)
收集(Collection) :这类操作将流中的元素收集到一个集合或其他可迭代结构中。
-
collect(Collector<? super T,A,R> collector)
,其中Collector
是一个接口,定义了如何将流元素转换为一个汇总结果。Java标准库提供了多种预定义的Collector
实现,如toList()
、toSet()
、toMap()
等。
List<String> words = Arrays.asList("Java", "Kotlin", "C#", "Python","JavaScript", "PHP");
// List集合转 Set集合
Set<String> uniqueWords = words.stream().collect(Collectors.toSet());
System.out.println(uniqueWords);//输出: [C#, Java, JavaScript, PHP, Kotlin, Python]
遍历(Traversal) :这些操作主要用于对流中的每个元素执行某个动作,但不产生聚合结果。
- 最典型的遍历操作是
forEach(Consumer<? super T> action)
,它接收一个Consumer
函数并应用于流中的每个元素。
List<String> words = Arrays.asList("Java", "Kotlin", "C#", "Python","JavaScript", "PHP");
// 遍历集合的每个元素
words.stream().forEach(System.out::println);
匹配(Matching) :匹配操作检查流中的元素是否满足特定条件,并返回一个布尔值作为结果。常用的匹配操作包括:
-
anyMatch(Predicate<? super T> predicate)
:检查流中是否存在至少一个元素满足给定的谓词条件。 -
allMatch(Predicate<? super T> predicate)
:检查流中是否所有元素都满足给定的谓词条件。 -
noneMatch(Predicate<? super T> predicate)
:检查流中是否没有任何元素满足给定的谓词条件。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5,7,8,9);
// 判断集合中的元素是否有偶数
boolean hasEvenNumber = numbers.stream().anyMatch(n -> n % 2 == 0);
System.out.println(hasEvenNumber);// 输出: true
查找与统计(Find & Count) :查找操作返回流中的某个特定元素或满足特定条件的元素,统计操作则计算元素的数量。
-
findFirst()
、findAny()
:返回流中的第一个元素或任意一个元素(对于并行流而言)。 -
count()
:返回流中元素的总数。 -
distinct()
:返回由流中唯一元素组成的流,相当于去重操作。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5,7,8,9);
// 查找第一个偶数
Optional<Integer> firstEven = numbers.stream()
.filter(n -> n % 2 == 0)
.findFirst();
System.out.println(firstEven.orElse(-1));//输出: 2
// 获取奇数的个数
long oddCount = numbers.stream()
.filter(n -> n % 2 != 0)
.count();
System.out.println(oddCount); // 输出:5
注意事项:
- Stream只能消费一次:一旦执行了终止操作,Stream就被视为消耗掉,无法再次使用。如果需要重复使用,需要重新创建Stream。
-
Stream操作是无状态的或有状态的:无状态操作(如
filter
,map
)不依赖于Stream中的其他元素,而有状态操作(如sorted
,distinct
)需要查看所有元素才能做出决定。 - 并行Stream的性能提升:并行Stream可以利用多核处理器提高计算速度,但并非所有操作都能从中受益,且可能会增加额外的开销。使用时应根据具体场景评估是否值得开启并行。