Stream
下面是一份关于Java中Stream API的完整、详细的用法及其作用的说明,内容涵盖了概念、创建方式、中间操作、终止操作、并行处理及注意事项。
一、Stream API概述
Java 8引入的Stream API为集合及数组提供了一种声明式、函数式的数据处理方式。与传统的集合操作相比,Stream具有以下几个显著特点:
- 非数据结构
Stream并不存储数据,它只是对数据源(如集合、数组、I/O通道等)的一个视图,所有的操作都是在数据源上进行计算的,这就避免了不必要的数据复制。 - 惰性求值
中间操作(如filter、map、sorted等)不会立即执行,只有在调用终止操作(例如forEach、collect等)时,才会统一触发整个操作链的计算,从而实现高效计算 - 不可重复使用
一个Stream一旦执行完终止操作后就会被“消费”,不能再使用;如果需要再次操作,需要重新生成新的Stream - 支持并行处理
Stream API提供了parallelStream()方法或通过parallel()将顺序流转换为并行流,利用多核处理器以更高效地执行大数据量的操作
二、Stream的创建
可以通过多种方式创建Stream,常见方法包括:
从集合创建
使用集合的stream()
或parallelStream()
方法即可。例如:1
2
3List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // 顺序流
Stream<String> parallelStream = list.parallelStream(); // 并行流从数组创建
利用Arrays.stream()
方法:1
2Integer[] nums = {1, 2, 3, 4, 5};
Stream<Integer> stream = Arrays.stream(nums);使用静态工厂方法
如Stream.of()
、Stream.iterate()
、Stream.generate()
等:1
2
3
4
5
6// 直接创建
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
// 生成无限流:例如生成0开始,每次加2的数列(通过limit限制个数)
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2).limit(6);
evenNumbers.forEach(System.out::println);从I/O流创建
如使用BufferedReader.lines()
从文件中获取每一行生成Stream:1
2
3
4
5
6try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
Stream<String> lines = reader.lines();
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
三、Stream的中间操作
中间操作返回的是一个新的Stream对象,可以对数据进行转换、筛选等处理,并支持链式调用。常见的中间操作包括:
filter(Predicate predicate)
用于过滤符合条件的元素:1
2
3
4
5List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evens = nums.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// 输出:[2, 4, 6]map(Function<T, R> mapper)
将每个元素转换成另一种形式,例如映射成平方数:1
2
3
4
5List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squares = nums.stream()
.map(n -> n * n)
.collect(Collectors.toList());
// 输出:[1, 4, 9, 16]flatMap(Function<T, Stream> mapper)
用于处理一对多映射关系,将嵌套结构扁平化:1
2
3
4
5
6
7
8List<List<String>> nested = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d")
);
List<String> flatList = nested.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
// 输出:[a, b, c, d]sorted() / sorted(Comparator)
对流中的元素进行排序:1
2
3
4
5List<String> names = Arrays.asList("Tom", "Alice", "Bob");
List<String> sortedNames = names.stream()
.sorted()
.collect(Collectors.toList());
// 输出:[Alice, Bob, Tom]distinct()
去除重复元素:1
2
3
4
5List<Integer> nums = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
List<Integer> uniqueNums = nums.stream()
.distinct()
.collect(Collectors.toList());
// 输出:[1, 2, 3, 4, 5]【citeturn0search6】
limit(long maxSize) 与 skip(long n)
分别用于限制流中元素的个数和跳过前n个元素:1
2
3
4
5
6
7
8
9
10List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> firstFive = nums.stream()
.limit(5)
.collect(Collectors.toList());
// 输出:[1, 2, 3, 4, 5]
List<Integer> afterSkip = nums.stream()
.skip(5)
.collect(Collectors.toList());
// 输出:[6, 7, 8, 9, 10]peek(Consumer action)
用于在流的每个元素上执行操作(通常用于调试),但不会改变流的元素:1
2
3
4List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.peek(System.out::println)
.collect(Collectors.toList());
四、Stream的终止操作
终止操作会触发整个流的计算,并产生最终结果。常见终止操作包括:
forEach(Consumer action)
遍历流中的每个元素:1
2List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().forEach(System.out::println);collect(Collector<T, A, R> collector)
将流中元素收集到集合、数组或其他数据结构中:1
2
3List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> listResult = nums.stream()
.collect(Collectors.toList());例如,还可以使用
Collectors.joining()
将字符串连接成一个整体【citeturn0search3】。reduce(BinaryOperator accumulator)
将流中的元素进行规约(如求和、求最大值):1
2
3
4
5
6List<Integer> nums = Arrays.asList(1, 2, 3, 4);
// 无起始值,返回Optional
Optional<Integer> sumOpt = nums.stream().reduce(Integer::sum);
// 有起始值
int sum = nums.stream().reduce(0, Integer::sum);
// 输出:10count()、min()、max()
分别用于统计个数、求最小/最大值:1
2
3long count = nums.stream().count();
int min = nums.stream().min(Integer::compare).get();
int max = nums.stream().max(Integer::compare).get();findFirst() 和 findAny()
分别用于查找第一个元素和任意一个元素(对于并行流,findAny可能更高效)【citeturn0search6】。
五、Stream的并行处理
利用并行流,可以轻松利用多核CPU实现并行处理,只需调用parallelStream()
或将顺序流转换为并行流:
1 | List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6); |
注意:并行流在处理有顺序要求的场景下可能会改变结果的顺序,且部分操作可能需要保证线程安全【citeturn0search7】。
六、Stream API的作用与优点
- 简洁易读
通过链式调用,代码更加声明式,避免了繁琐的循环和临时变量。 - 高效处理
惰性求值和内部迭代机制使得Stream在大数据量下也能高效执行,同时内置的并行处理可充分利用多核性能。 - 灵活性强
可对数据进行各种转换(过滤、映射、扁平化、归约等),并且能方便地转换成集合、数组或字符串。 - 函数式编程风格
使代码更具模块化和可组合性,更易于测试和维护。
七、注意事项
- 一次性使用
Stream在执行终止操作后不能再重复使用;需要时请重新生成。 - 避免副作用
尽量保持中间操作无副作用,尤其在并行流中,避免修改共享变量。 - 有状态操作成本较高
如sorted、distinct等操作可能需要缓存数据,在大数据集上使用时需注意性能和内存问题。 - 顺序与并行
在并行流中,如果需要保持元素顺序,则可能会牺牲部分性能;对于无顺序要求的场景可以使用unordered()或直接使用parallelStream()。
总结
Java的Stream API极大地提高了对集合数据进行处理的灵活性和表达能力。它将复杂的数据操作简化为一系列声明式的转换,通过中间操作与终止操作的组合,不仅使代码更简洁、可读性更高,还能利用并行处理提高性能。理解Stream的“惰性求值”、“一次性使用”以及各类常见操作(如filter、map、reduce等)的用法,将有助于你编写出高效、优雅的Java代码。
以上就是关于Java中Stream的完整详细用法及其作用的说明,希望能帮助你更深入地掌握这一强大特性。 Happy coding!
Lambda
下面是一份关于 Java Lambda 表达式的完整说明,涵盖了其语法、用途、常见场景及注意事项,帮助你全面理解这一 Java 8 引入的重要特性。
一、Lambda 表达式简介
Lambda 表达式是 Java 8 引入的一种语法,用于表示匿名函数(即没有名字的方法)。它允许你以一种简洁、声明式的方式将行为(代码块)作为参数传递给方法,从而大大减少样板代码,并使代码更具函数式风格。
主要作用
:
- 简化代码:减少匿名内部类的冗余代码。
- 支持函数式编程:使得代码可以像操作数据一样操作行为。
- 与 Stream API 配合:为集合操作提供便捷的过滤、映射、归约等功能。
二、Lambda 表达式的语法
Lambda 表达式的基本语法格式如下:
1 | (parameters) -> expression |
或者包含多条语句的代码块:
1 | (parameters) -> { |
- 参数列表:可以省略参数类型(由编译器推断),如果只有一个参数,也可以省略圆括号。
- **箭头符号 (
->
)**:将参数与执行体分隔。 - 表达式或代码块:表达式形式会自动返回其计算结果;代码块需要使用
return
明确返回值(如果方法签名要求返回值)。
例如,对一个字符串列表进行排序可以写成:
1 | List<String> names = Arrays.asList("Tom", "Alice", "Bob"); |
如果只有一个参数,则可以简化为:
1 | list.forEach(s -> System.out.println(s)); |
三、函数式接口(Functional Interface)
Lambda 表达式只能用于实现函数式接口。所谓函数式接口,就是只包含一个抽象方法的接口。Java 标准库中已有大量常用的函数式接口,如:
- Runnable(无参数,无返回值)
- Comparator(接受两个参数,返回比较结果)
- Consumer(接受一个参数,无返回值)
- Supplier(无参数,返回一个结果)
- **Function<T,R>**(接受一个参数,返回一个结果)
你还可以使用 @FunctionalInterface
注解来标记接口,以确保其符合函数式接口的要求。
例如:
1 |
|
然后可以用 Lambda 表达式实现这个接口:
1 | MyFunction add = (a, b) -> a + b; |
四、Lambda 表达式的使用场景及作用
1. 简化代码
使用 Lambda 表达式,可以大幅简化匿名内部类的写法。比如,原来的 Runnable:
1 | Runnable r = new Runnable() { |
可以简化为:
1 | Runnable r = () -> System.out.println("Hello, world!"); |
2. 与集合及 Stream API 结合
在集合操作中,Lambda 表达式通常与 Stream API 配合使用,能以声明式风格完成过滤、映射、排序等操作。例如:
1 | List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); |
这种写法使得数据操作更直观、易读。
3. 事件处理及回调
在 GUI 开发或异步编程中,Lambda 表达式可以用于注册事件监听器、回调函数等。例如,在 Swing 中:
1 | button.addActionListener(e -> System.out.println("Button clicked!")); |
4. 多线程
在多线程环境下,Lambda 表达式常用于简化线程创建,比如传入 Runnable 参数:
1 | new Thread(() -> { |
五、Lambda 表达式中的变量作用域
Lambda 表达式可以访问其作用域内的局部变量,但这些变量必须是“有效最终”(effectively final)的,也就是说,在 Lambda 表达式内不能对其修改。这一点与匿名内部类相同,有助于确保线程安全和一致性。
六、方法引用与构造器引用
为了使 Lambda 表达式更简洁,Java 还提供了方法引用和构造器引用的语法:
方法引用
:使用
1
ClassName::methodName
替代 Lambda 表达式,例如:
1
2List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(System.out::println);构造器引用
:使用
1
ClassName::new
,例如:
1
2Supplier<ArrayList<String>> supplier = ArrayList::new;
ArrayList<String> list = supplier.get();
七、完整示例
下面是一个综合示例,演示如何用 Lambda 表达式实现对一个学生集合的筛选、排序及映射操作:
1 | import java.util.*; |
这段代码利用 Lambda 表达式对学生数据进行筛选、排序和打印,同时还展示了如何用 Lambda 实现一个自定义的函数式接口。
八、总结
Java Lambda 表达式使代码更加简洁、灵活,能够将行为作为参数传递,从而实现函数式编程风格。它不仅极大地简化了匿名内部类的写法,还与 Stream API、事件处理、多线程等场景完美结合,帮助开发者编写出更高效、更易读的代码。在使用时请注意 Lambda 表达式中变量的作用域(必须是有效最终的)以及与函数式接口的配合使用。
希望这份详细说明能够帮助你全面掌握 Java Lambda 表达式的用法及其作用。 Happy coding!