0%

Stream&Lambda

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,常见方法包括:

  1. 从集合创建
    使用集合的stream()parallelStream()方法即可。例如:

    1
    2
    3
    List<String> list = Arrays.asList("a", "b", "c");
    Stream<String> stream = list.stream(); // 顺序流
    Stream<String> parallelStream = list.parallelStream(); // 并行流
  2. 从数组创建
    利用Arrays.stream()方法:

    1
    2
    Integer[] nums = {1, 2, 3, 4, 5};
    Stream<Integer> stream = Arrays.stream(nums);
  3. 使用静态工厂方法
    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);
  4. 从I/O流创建
    如使用BufferedReader.lines()从文件中获取每一行生成Stream:

    1
    2
    3
    4
    5
    6
    try (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对象,可以对数据进行转换、筛选等处理,并支持链式调用。常见的中间操作包括:

  1. filter(Predicate predicate)
    用于过滤符合条件的元素:

    1
    2
    3
    4
    5
    List<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]
  2. map(Function<T, R> mapper)
    将每个元素转换成另一种形式,例如映射成平方数:

    1
    2
    3
    4
    5
    List<Integer> nums = Arrays.asList(1, 2, 3, 4);
    List<Integer> squares = nums.stream()
    .map(n -> n * n)
    .collect(Collectors.toList());
    // 输出:[1, 4, 9, 16]
  3. flatMap(Function<T, Stream> mapper)
    用于处理一对多映射关系,将嵌套结构扁平化:

    1
    2
    3
    4
    5
    6
    7
    8
    List<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]
  4. sorted() / sorted(Comparator)
    对流中的元素进行排序:

    1
    2
    3
    4
    5
    List<String> names = Arrays.asList("Tom", "Alice", "Bob");
    List<String> sortedNames = names.stream()
    .sorted()
    .collect(Collectors.toList());
    // 输出:[Alice, Bob, Tom]
  5. distinct()
    去除重复元素:

    1
    2
    3
    4
    5
    List<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]

    【citeturn0search6】

  6. limit(long maxSize)skip(long n)
    分别用于限制流中元素的个数和跳过前n个元素:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    List<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]
  7. peek(Consumer action)
    用于在流的每个元素上执行操作(通常用于调试),但不会改变流的元素:

    1
    2
    3
    4
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
    names.stream()
    .peek(System.out::println)
    .collect(Collectors.toList());

四、Stream的终止操作

终止操作会触发整个流的计算,并产生最终结果。常见终止操作包括:

  1. forEach(Consumer action)
    遍历流中的每个元素:

    1
    2
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
    names.stream().forEach(System.out::println);
  2. collect(Collector<T, A, R> collector)
    将流中元素收集到集合、数组或其他数据结构中:

    1
    2
    3
    List<Integer> nums = Arrays.asList(1, 2, 3, 4);
    List<Integer> listResult = nums.stream()
    .collect(Collectors.toList());

    例如,还可以使用Collectors.joining()将字符串连接成一个整体【citeturn0search3】。

  3. reduce(BinaryOperator accumulator)
    将流中的元素进行规约(如求和、求最大值):

    1
    2
    3
    4
    5
    6
    List<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);
    // 输出:10
  4. count()、min()、max()
    分别用于统计个数、求最小/最大值:

    1
    2
    3
    long count = nums.stream().count();
    int min = nums.stream().min(Integer::compare).get();
    int max = nums.stream().max(Integer::compare).get();
  5. findFirst() 和 findAny()
    分别用于查找第一个元素和任意一个元素(对于并行流,findAny可能更高效)【citeturn0search6】。


五、Stream的并行处理

利用并行流,可以轻松利用多核CPU实现并行处理,只需调用parallelStream()或将顺序流转换为并行流:

1
2
3
4
5
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> result = nums.parallelStream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.collect(Collectors.toList());

注意:并行流在处理有顺序要求的场景下可能会改变结果的顺序,且部分操作可能需要保证线程安全【citeturn0search7】。


六、Stream API的作用与优点

  1. 简洁易读
    通过链式调用,代码更加声明式,避免了繁琐的循环和临时变量。
  2. 高效处理
    惰性求值和内部迭代机制使得Stream在大数据量下也能高效执行,同时内置的并行处理可充分利用多核性能。
  3. 灵活性强
    可对数据进行各种转换(过滤、映射、扁平化、归约等),并且能方便地转换成集合、数组或字符串。
  4. 函数式编程风格
    使代码更具模块化和可组合性,更易于测试和维护。

七、注意事项

  • 一次性使用
    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
2
3
4
5
6
7
(parameters) -> {
// 多条语句
statement1;
statement2;
// ……
return result; // 如果需要返回值
}
  • 参数列表:可以省略参数类型(由编译器推断),如果只有一个参数,也可以省略圆括号。
  • **箭头符号 (->)**:将参数与执行体分隔。
  • 表达式或代码块:表达式形式会自动返回其计算结果;代码块需要使用 return 明确返回值(如果方法签名要求返回值)。

例如,对一个字符串列表进行排序可以写成:

1
2
List<String> names = Arrays.asList("Tom", "Alice", "Bob");
names.sort((s1, s2) -> s1.compareTo(s2));

如果只有一个参数,则可以简化为:

1
list.forEach(s -> System.out.println(s));

三、函数式接口(Functional Interface)

Lambda 表达式只能用于实现函数式接口。所谓函数式接口,就是只包含一个抽象方法的接口。Java 标准库中已有大量常用的函数式接口,如:

  • Runnable(无参数,无返回值)
  • Comparator(接受两个参数,返回比较结果)
  • Consumer(接受一个参数,无返回值)
  • Supplier(无参数,返回一个结果)
  • **Function<T,R>**(接受一个参数,返回一个结果)

你还可以使用 @FunctionalInterface 注解来标记接口,以确保其符合函数式接口的要求。

例如:

1
2
3
4
@FunctionalInterface
public interface MyFunction {
int operate(int a, int b);
}

然后可以用 Lambda 表达式实现这个接口:

1
2
MyFunction add = (a, b) -> a + b;
System.out.println(add.operate(3, 4)); // 输出 7

四、Lambda 表达式的使用场景及作用

1. 简化代码

使用 Lambda 表达式,可以大幅简化匿名内部类的写法。比如,原来的 Runnable:

1
2
3
4
5
6
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, world!");
}
};

可以简化为:

1
Runnable r = () -> System.out.println("Hello, world!");

2. 与集合及 Stream API 结合

在集合操作中,Lambda 表达式通常与 Stream API 配合使用,能以声明式风格完成过滤、映射、排序等操作。例如:

1
2
3
4
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());

这种写法使得数据操作更直观、易读。

3. 事件处理及回调

在 GUI 开发或异步编程中,Lambda 表达式可以用于注册事件监听器、回调函数等。例如,在 Swing 中:

1
button.addActionListener(e -> System.out.println("Button clicked!"));

4. 多线程

在多线程环境下,Lambda 表达式常用于简化线程创建,比如传入 Runnable 参数:

1
2
3
4
new Thread(() -> {
// 执行任务
System.out.println("Running in a thread");
}).start();

五、Lambda 表达式中的变量作用域

Lambda 表达式可以访问其作用域内的局部变量,但这些变量必须是“有效最终”(effectively final)的,也就是说,在 Lambda 表达式内不能对其修改。这一点与匿名内部类相同,有助于确保线程安全和一致性。


六、方法引用与构造器引用

为了使 Lambda 表达式更简洁,Java 还提供了方法引用和构造器引用的语法:

  • 方法引用

    :使用

    1
    ClassName::methodName

    替代 Lambda 表达式,例如:

    1
    2
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
    names.forEach(System.out::println);
  • 构造器引用

    :使用

    1
    ClassName::new

    ,例如:

    1
    2
    Supplier<ArrayList<String>> supplier = ArrayList::new;
    ArrayList<String> list = supplier.get();

七、完整示例

下面是一个综合示例,演示如何用 Lambda 表达式实现对一个学生集合的筛选、排序及映射操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import java.util.*;
import java.util.stream.Collectors;

@FunctionalInterface
interface ScoreCalculator {
double calculate(double score1, double score2);
}

class Student {
private String name;
private int age;
private double score;

public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}

public String getName() { return name; }
public int getAge() { return age; }
public double getScore() { return score; }

@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + ", score=" + score + "}";
}
}

public class LambdaDemo {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("Alice", 20, 90.0),
new Student("Bob", 22, 85.5),
new Student("Charlie", 19, 88.5),
new Student("David", 21, 92.0),
new Student("Eva", 18, 94.5)
);

// 过滤出年龄大于18且成绩不低于90的学生,并按成绩降序排序
List<Student> filtered = students.stream()
.filter(s -> s.getAge() > 18 && s.getScore() >= 90.0)
.sorted(Comparator.comparingDouble(Student::getScore).reversed())
.collect(Collectors.toList());

filtered.forEach(System.out::println);

// 使用自定义函数式接口计算两个分数的平均值
ScoreCalculator avgCalc = (s1, s2) -> (s1 + s2) / 2;
System.out.println("平均成绩:" + avgCalc.calculate(90.0, 92.0));
}
}

这段代码利用 Lambda 表达式对学生数据进行筛选、排序和打印,同时还展示了如何用 Lambda 实现一个自定义的函数式接口。


八、总结

Java Lambda 表达式使代码更加简洁、灵活,能够将行为作为参数传递,从而实现函数式编程风格。它不仅极大地简化了匿名内部类的写法,还与 Stream API、事件处理、多线程等场景完美结合,帮助开发者编写出更高效、更易读的代码。在使用时请注意 Lambda 表达式中变量的作用域(必须是有效最终的)以及与函数式接口的配合使用。

希望这份详细说明能够帮助你全面掌握 Java Lambda 表达式的用法及其作用。 Happy coding!