Java Stream流详解

1. Stream流概述

1.1 什么是Stream流

Stream流是Java 8引入的一种处理数据集合的API,它允许以声明式方式处理数据集合,让开发者能够写出更加简洁、高效的代码。Stream流的设计初衷是提供一种函数式编程的方式来处理集合,使代码更具可读性,并可以利用多核架构实现并行操作。

Stream流不是一种数据结构,它只是数据源的一个视图,不会存储数据,也不会修改源数据。通常,Stream流的操作是延迟执行的,这意味着它们直到需要结果时才会计算。

1.2 Stream流与传统集合操作的区别

下面通过一个简单的例子来对比传统集合操作和使用Stream流操作的区别:

需求:从一个整数列表中找出所有大于3的偶数,并将这些数字乘以2后输出。

传统方式

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> result = new ArrayList<>();

// 找出大于3的偶数
for (Integer number : numbers) {
    if (number > 3 && number % 2 == 0) {
        result.add(number);
    }
}

// 将结果乘以2
List<Integer> doubledResult = new ArrayList<>();
for (Integer number : result) {
    doubledResult.add(number * 2);
}

// 输出结果
for (Integer number : doubledResult) {
    System.out.println(number);
}

使用Stream流

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

numbers.stream()
       .filter(n -> n > 3)         // 过滤大于3的数
       .filter(n -> n % 2 == 0)    // 过滤偶数
       .map(n -> n * 2)            // 将每个数乘以2
       .forEach(System.out::println); // 输出结果

从上面的例子可以看出,使用Stream流的代码更加简洁、清晰,操作链式连接,可读性更高。

1.3 Stream流的特点

  1. 声明式:描述要做什么,而不是如何做,隐藏了实现细节。
  2. 可链式调用:支持多种操作的链式调用,形成一个操作管道。
  3. 延迟执行:只有在最终操作被调用时,之前的操作才会执行。
  4. 可并行化:轻松实现并行处理,提高性能。
  5. 不可重复使用:Stream流一旦被消费,就不能再次使用。

1.4 Stream流的基本组成

一个完整的Stream流操作通常包含三个部分:

  1. 数据源:创建Stream流的来源,如集合、数组等。
  2. 中间操作:对Stream流中的元素进行处理的操作,如过滤、映射等,中间操作可以有多个,并且是延迟执行的。
  3. 终端操作:触发Stream流执行的操作,如收集、遍历等,一个Stream流只能有一个终端操作,且执行后Stream流就会被关闭。
Arrays.asList(1, 2, 3, 4, 5)  // 数据源
      .stream()               // 创建Stream流
      .filter(n -> n % 2 == 0) // 中间操作:过滤
      .map(n -> n * 2)         // 中间操作:映射
      .collect(Collectors.toList()); // 终端操作:收集

2. 创建Stream流的方式

Java提供了多种创建Stream流的方式,下面详细介绍各种常见的创建方式。

2.1 从集合创建Stream流

Java 8为Collection接口添加了stream()方法,所有集合类都可以通过此方法创建Stream流。

// 从List创建Stream流
List<String> list = Arrays.asList("Java", "Python", "C++", "JavaScript");
Stream<String> streamFromList = list.stream();

// 从Set创建Stream流
Set<String> set = new HashSet<>(list);
Stream<String> streamFromSet = set.stream();

// 从Map创建Stream流
Map<String, Integer> map = new HashMap<>();
map.put("Java", 1);
map.put("Python", 2);
map.put("C++", 3);

// 获取键的Stream流
Stream<String> keyStream = map.keySet().stream();

// 获取值的Stream流
Stream<Integer> valueStream = map.values().stream();

// 获取键值对的Stream流
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

2.2 从数组创建Stream流

使用Arrays.stream()或者Stream.of()方法可以从数组创建Stream流。

// 使用Arrays.stream()
String[] array = {"Java", "Python", "C++", "JavaScript"};
Stream<String> streamFromArray1 = Arrays.stream(array);

// 也可以指定数组的起始和结束位置
Stream<String> streamFromArray2 = Arrays.stream(array, 1, 3); // 包含索引1,不包含索引3

// 使用Stream.of()
Stream<String> streamFromArray3 = Stream.of(array);

// 也可以直接传入多个元素
Stream<String> streamFromVarargs = Stream.of("Java", "Python", "C++", "JavaScript");

2.3 创建数值Stream流

为了避免基本数据类型的装箱和拆箱操作,Java 8提供了特殊的数值Stream流:IntStreamLongStreamDoubleStream

// 创建IntStream
IntStream intStream1 = IntStream.range(1, 5);      // 创建1,2,3,4
IntStream intStream2 = IntStream.rangeClosed(1, 5); // 创建1,2,3,4,5

// 使用map操作时,可以使用专门的mapToInt, mapToLong, mapToDouble方法
List<String> list = Arrays.asList("Java", "Python", "C++");
IntStream lengthStream = list.stream()
                            .mapToInt(String::length); // 将字符串映射为其长度

// 从数组创建
int[] intArray = {1, 2, 3, 4, 5};
IntStream intStreamFromArray = Arrays.stream(intArray);

// 使用of方法
IntStream intStreamFromOf = IntStream.of(1, 2, 3, 4, 5);

2.4 创建无限Stream流

使用Stream.iterate()Stream.generate()方法可以创建无限Stream流。

// 使用iterate创建无限Stream流
// 第一个参数是初始值,第二个参数是一个函数,用于计算下一个元素
Stream<Integer> infiniteStream1 = Stream.iterate(0, n -> n + 2); // 生成偶数: 0, 2, 4, 6, ...

// Java 9中增加了一个重载方法,可以指定终止条件
// Stream<Integer> finiteStream = Stream.iterate(0, n -> n < 10, n -> n + 2); // 生成 0, 2, 4, 6, 8

// 使用generate创建无限Stream流
// 参数是一个Supplier函数,用于生成元素
Stream<Double> infiniteStream2 = Stream.generate(Math::random); // 生成随机数

// 无限Stream流需要使用limit等操作来限制元素数量,否则可能导致无限循环
Stream<Integer> limitedStream = infiniteStream1.limit(5); // 只取前5个元素:0, 2, 4, 6, 8
limitedStream.forEach(System.out::println);

2.5 从文件创建Stream流

Java 8的Files类提供了多种方法来创建Stream流,例如从文件中获取Stream流:

try {
    // 读取文件中的每一行,创建Stream流
    Stream<String> linesFromFile = Files.lines(Paths.get("file.txt"));
    
    // 或者列出目录中的文件
    Stream<Path> filesInDir = Files.list(Paths.get("."));
    
    // 使用完Stream流后记得关闭
    linesFromFile.close();
    filesInDir.close();
} catch (IOException e) {
    e.printStackTrace();
}

2.6 空Stream流

可以使用Stream.empty()方法创建一个空的Stream流:

Stream<String> emptyStream = Stream.empty();

2.7 合并Stream流

Java 8提供了Stream.concat()方法来合并两个Stream流:

Stream<String> stream1 = Stream.of("Java", "Python");
Stream<String> stream2 = Stream.of("C++", "JavaScript");
Stream<String> mergedStream = Stream.concat(stream1, stream2);

// 输出: Java, Python, C++, JavaScript
mergedStream.forEach(System.out::println);

2.8 实际应用示例

下面是一个综合示例,展示了如何使用不同方式创建Stream流并进行简单操作:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamCreationExample {
    public static void main(String[] args) {
        // 1. 从集合创建Stream流
        List<String> languages = Arrays.asList("Java", "Python", "C++", "JavaScript");
        System.out.println("从List创建Stream流:");
        languages.stream()
                .filter(lang -> lang.startsWith("J"))
                .forEach(System.out::println);
                
        // 2. 从数组创建Stream流
        String[] languageArray = {"Java", "Python", "C++", "JavaScript"};
        System.out.println("\n从数组创建Stream流:");
        Arrays.stream(languageArray)
              .map(String::toUpperCase)
              .forEach(System.out::println);
              
        // 3. 创建数值Stream流
        System.out.println("\n创建数值Stream流:");
        IntStream.rangeClosed(1, 5)
                .map(n -> n * n)
                .forEach(System.out::println);
                
        // 4. 创建无限Stream流并限制数量
        System.out.println("\n创建无限Stream流(斐波那契数列的前10个数):");
        Stream.iterate(new int[]{0, 1}, arr -> new int[]{arr[1], arr[0] + arr[1]})
              .limit(10)
              .map(arr -> arr[0])
              .forEach(System.out::println);
              
        // 5. 从文件创建Stream流
        System.out.println("\n从文件创建Stream流:");
        try {
            Files.lines(Paths.get("example.txt"))
                 .limit(3) // 只读取前3行
                 .forEach(System.out::println);
        } catch (IOException e) {
            System.out.println("文件不存在或读取错误: " + e.getMessage());
        }
        
        // 6. 创建空Stream流
        System.out.println("\n创建空Stream流:");
        long count = Stream.empty().count();
        System.out.println("空Stream流的元素数量: " + count);
        
        // 7. 合并Stream流
        System.out.println("\n合并Stream流:");
        Stream<String> frontend = Stream.of("HTML", "CSS", "JavaScript");
        Stream<String> backend = Stream.of("Java", "Python", "Node.js");
        Stream.concat(frontend, backend)
              .sorted()
              .forEach(lang -> System.out.print(lang + " "));
    }
}

通过上面的例子,我们可以看到创建Stream流的多种方式以及简单的操作示例。在实际应用中,可以根据数据源和需求选择合适的方式创建Stream流。

3. Stream流的中间操作

中间操作(Intermediate Operations)是Stream流处理过程中的一个重要环节,它们对流中的元素进行处理后返回一个新的流,允许我们进行链式调用。中间操作的一个重要特点是它们是延迟执行的,即只有在终端操作执行时才会触发。

3.1 过滤操作(filter)

filter方法接收一个谓词(Predicate)作为参数,用于过滤流中的元素。

List<String> languages = Arrays.asList("Java", "Python", "JavaScript", "C#", "C++", "Kotlin");

// 过滤以J开头的语言
List<String> jLanguages = languages.stream()
                                  .filter(lang -> lang.startsWith("J"))
                                  .collect(Collectors.toList());
System.out.println("以J开头的语言:" + jLanguages); // [Java, JavaScript]

// 过滤长度大于5的语言
List<String> longNameLanguages = languages.stream()
                                        .filter(lang -> lang.length() > 5)
                                        .collect(Collectors.toList());
System.out.println("名称长度大于5的语言:" + longNameLanguages); // [Python, JavaScript, Kotlin]

// 组合多个过滤条件
List<String> combined = languages.stream()
                              .filter(lang -> lang.contains("a"))
                              .filter(lang -> lang.length() < 6)
                              .collect(Collectors.toList());
System.out.println("包含字母a且长度小于6的语言:" + combined); // [Java]

3.2 映射操作(map, flatMap)

3.2.1 map

map方法接收一个函数作为参数,用于将流中的元素映射成新的元素。

List<String> languages = Arrays.asList("Java", "Python", "JavaScript");

// 将语言名称转换为大写
List<String> upperCaseLanguages = languages.stream()
                                         .map(String::toUpperCase)
                                         .collect(Collectors.toList());
System.out.println("大写语言名称:" + upperCaseLanguages); // [JAVA, PYTHON, JAVASCRIPT]

// 获取语言名称的长度
List<Integer> languageLengths = languages.stream()
                                        .map(String::length)
                                        .collect(Collectors.toList());
System.out.println("语言名称长度:" + languageLengths); // [4, 6, 10]

// 使用自定义转换函数
List<String> descriptions = languages.stream()
                                   .map(lang -> lang + " is a programming language")
                                   .collect(Collectors.toList());
System.out.println("语言描述:" + descriptions);
// [Java is a programming language, Python is a programming language, JavaScript is a programming language]
3.2.2 flatMap

flatMap方法接收一个函数作为参数,该函数将流中的每个元素映射为一个新的流,然后将所有流连接成一个流。它主要用于处理嵌套集合。

// 扁平化嵌套列表
List<List<Integer>> nestedNumbers = Arrays.asList(
    Arrays.asList(1, 2, 3),
    Arrays.asList(4, 5, 6),
    Arrays.asList(7, 8, 9)
);

List<Integer> flattenedNumbers = nestedNumbers.stream()
                                            .flatMap(Collection::stream)
                                            .collect(Collectors.toList());
System.out.println("扁平化后的数字:" + flattenedNumbers); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// 获取多个字符串中的所有不重复字符
List<String> words = Arrays.asList("Hello", "World");
List<String> uniqueChars = words.stream()
                              .map(word -> word.split(""))
                              .flatMap(Arrays::stream)
                              .distinct()
                              .collect(Collectors.toList());
System.out.println("所有不重复字符:" + uniqueChars); // [H, e, l, o, W, r, d]

// 生成笛卡尔积
List<String> list1 = Arrays.asList("A", "B");
List<String> list2 = Arrays.asList("1", "2", "3");
List<String> cartesianProduct = list1.stream()
                                  .flatMap(item1 ->
                                       list2.stream()
                                           .map(item2 -> item1 + item2)
                                  )
                                  .collect(Collectors.toList());
System.out.println("笛卡尔积:" + cartesianProduct); // [A1, A2, A3, B1, B2, B3]

3.3 排序操作(sorted)

sorted方法用于对流中的元素进行排序。

List<String> languages = Arrays.asList("Java", "Python", "C++", "JavaScript", "Kotlin");

// 自然排序(字母顺序)
List<String> sortedLanguages = languages.stream()
                                       .sorted()
                                       .collect(Collectors.toList());
System.out.println("排序后的语言:" + sortedLanguages); // [C++, Java, JavaScript, Kotlin, Python]

// 使用自定义比较器,按字符串长度排序
List<String> lengthSorted = languages.stream()
                                   .sorted(Comparator.comparing(String::length))
                                   .collect(Collectors.toList());
System.out.println("按长度排序的语言:" + lengthSorted); // [C++, Java, Python, Kotlin, JavaScript]

// 反向排序
List<String> reverseSorted = languages.stream()
                                    .sorted(Comparator.reverseOrder())
                                    .collect(Collectors.toList());
System.out.println("反向排序的语言:" + reverseSorted); // [Python, Kotlin, JavaScript, Java, C++]

// 多级排序:先按长度,然后按字母顺序
List<String> multiSorted = languages.stream()
                                  .sorted(Comparator.comparing(String::length)
                                         .thenComparing(Comparator.naturalOrder()))
                                  .collect(Collectors.toList());
System.out.println("多级排序的语言:" + multiSorted); // [C++, Java, Kotlin, Python, JavaScript]

3.4 去重操作(distinct)

distinct方法用于去除流中的重复元素。

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 5, 5);

// 去除重复元素
List<Integer> distinctNumbers = numbers.stream()
                                      .distinct()
                                      .collect(Collectors.toList());
System.out.println("去重后的数字:" + distinctNumbers); // [1, 2, 3, 4, 5]

// 对自定义对象去重(需要正确实现equals和hashCode方法)
class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 省略getter、setter
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    
    @Override
    public String toString() {
        return name + "(" + age + ")";
    }
}

List<Person> persons = Arrays.asList(
    new Person("Alice", 25),
    new Person("Bob", 30),
    new Person("Alice", 25),
    new Person("Charlie", 35)
);

List<Person> distinctPersons = persons.stream()
                                    .distinct()
                                    .collect(Collectors.toList());
System.out.println("去重后的人员:" + distinctPersons); // [Alice(25), Bob(30), Charlie(35)]

3.5 截取操作(limit, skip)

limitskip方法用于截取流中的元素。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 获取前5个元素
List<Integer> first5 = numbers.stream()
                            .limit(5)
                            .collect(Collectors.toList());
System.out.println("前5个元素:" + first5); // [1, 2, 3, 4, 5]

// 跳过前5个元素
List<Integer> after5 = numbers.stream()
                            .skip(5)
                            .collect(Collectors.toList());
System.out.println("跳过前5个元素:" + after5); // [6, 7, 8, 9, 10]

// 结合使用,实现分页
List<Integer> page2Size3 = numbers.stream()
                                .skip(3) // 跳过第1页
                                .limit(3) // 获取第2页的3条记录
                                .collect(Collectors.toList());
System.out.println("第2页(每页3条):" + page2Size3); // [4, 5, 6]

3.6 查看操作(peek)

peek方法接收一个消费者(Consumer)作为参数,主要用于调试,它可以让我们在不中断流操作的情况下查看流中的元素。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 使用peek观察中间处理过程
List<Integer> result = numbers.stream()
                            .peek(n -> System.out.println("Initial: " + n))
                            .filter(n -> n % 2 == 0)
                            .peek(n -> System.out.println("After filter: " + n))
                            .map(n -> n * 10)
                            .peek(n -> System.out.println("After map: " + n))
                            .collect(Collectors.toList());

System.out.println("最终结果:" + result); // [20, 40]

// 输出:
// Initial: 1
// Initial: 2
// After filter: 2
// After map: 20
// Initial: 3
// Initial: 4
// After filter: 4
// After map: 40
// Initial: 5
// 最终结果:[20, 40]

3.7 实际应用示例

下面通过一个综合示例,展示多种中间操作的组合使用:

// 一个学生类
class Student {
    private String name;
    private int age;
    private String gender;
    private List<String> courses;
    
    public Student(String name, int age, String gender, List<String> courses) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.courses = courses;
    }
    
    // 省略getter、setter
    
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getGender() { return gender; }
    public List<String> getCourses() { return courses; }
    
    @Override
    public String toString() {
        return name + "(" + age + ", " + gender + ")";
    }
}

// 学生数据
List<Student> students = Arrays.asList(
    new Student("Alice", 20, "Female", Arrays.asList("Math", "Physics", "Chemistry")),
    new Student("Bob", 22, "Male", Arrays.asList("Computer Science", "Math")),
    new Student("Charlie", 19, "Male", Arrays.asList("Physics", "Biology")),
    new Student("Diana", 21, "Female", Arrays.asList("Computer Science", "Literature")),
    new Student("Eve", 20, "Female", Arrays.asList("Math", "Economics"))
);

// 获取所有女生选修的不重复课程,按字母排序
List<String> femaleStudentCourses = students.stream()
                                         .filter(s -> s.getGender().equals("Female"))
                                         .flatMap(s -> s.getCourses().stream())
                                         .distinct()
                                         .sorted()
                                         .collect(Collectors.toList());
System.out.println("女生选修的课程(去重排序):" + femaleStudentCourses);
// [Chemistry, Computer Science, Economics, Literature, Math, Physics]

// 获取年龄大于20岁的学生,按年龄降序,再按姓名升序
List<Student> sortedStudents = students.stream()
                                    .filter(s -> s.getAge() > 20)
                                    .sorted(Comparator.comparing(Student::getAge).reversed()
                                           .thenComparing(Student::getName))
                                    .collect(Collectors.toList());
System.out.println("年龄大于20岁的学生(按年龄降序,再按姓名升序):" + sortedStudents);
// [Bob(22, Male), Diana(21, Female)]

// 计算所有学生选修的课程总数(包含重复)
long totalCourseCount = students.stream()
                             .flatMap(s -> s.getCourses().stream())
                             .count();
System.out.println("所有学生选修的课程总数(包含重复):" + totalCourseCount); // 10

// 查找20岁学生的课程,并统计每门课程的选修人数
Map<String, Long> courseCountMap = students.stream()
                                       .filter(s -> s.getAge() == 20)
                                       .flatMap(s -> s.getCourses().stream())
                                       .collect(Collectors.groupingBy(
                                           course -> course,
                                           Collectors.counting()
                                       ));
System.out.println("20岁学生中各课程的选修人数:" + courseCountMap);
// {Economics=1, Physics=1, Chemistry=1, Math=2}

4. Stream流的终端操作

终端操作(Terminal Operations)是Stream流处理过程中的最后一个环节,它们触发Stream流执行,并返回一个结果。终端操作的一个重要特点是它们是立即执行的,即在调用时就会立即执行。

4.1 收集操作(collect)

collect方法用于将流中的元素收集到一个结果容器中。

List<String> languages = Arrays.asList("Java", "Python", "JavaScript", "C#", "C++", "Kotlin");

// 收集以J开头的语言
List<String> jLanguages = languages.stream()
                                  .filter(lang -> lang.startsWith("J"))
                                  .collect(Collectors.toList());
System.out.println("以J开头的语言:" + jLanguages); // [Java, JavaScript]

// 收集长度大于5的语言
List<String> longNameLanguages = languages.stream()
                                        .filter(lang -> lang.length() > 5)
                                        .collect(Collectors.toList());
System.out.println("名称长度大于5的语言:" + longNameLanguages); // [Python, JavaScript, Kotlin]

// 组合多个收集条件
List<String> combined = languages.stream()
                              .filter(lang -> lang.contains("a"))
                              .filter(lang -> lang.length() < 6)
                              .collect(Collectors.toList());
System.out.println("包含字母a且长度小于6的语言:" + combined); // [Java]

4.2 查找操作(find)

find方法用于查找流中的元素。

List<String> languages = Arrays.asList("Java", "Python", "JavaScript", "C#", "C++", "Kotlin");

// 查找以J开头的语言
Optional<String> jLanguage = languages.stream()
                                     .filter(lang -> lang.startsWith("J"))
                                     .findFirst();
System.out.println("以J开头的语言:" + jLanguage.orElse("没有找到")); // Java

// 查找长度大于5的语言
Optional<String> longNameLanguage = languages.stream()
                                           .filter(lang -> lang.length() > 5)
                                           .findFirst();
System.out.println("名称长度大于5的语言:" + longNameLanguage.orElse("没有找到")); // Python

// 组合多个查找条件
Optional<String> combined = languages.stream()
                                   .filter(lang -> lang.contains("a"))
                                   .filter(lang -> lang.length() < 6)
                                   .findFirst();
System.out.println("包含字母a且长度小于6的语言:" + combined.orElse("没有找到")); // Java

4.3 匹配操作(match)

match方法用于检查流中的元素是否满足某个条件。

List<String> languages = Arrays.asList("Java", "Python", "JavaScript", "C#", "C++", "Kotlin");

// 检查是否有以J开头的语言
boolean hasJLanguage = languages.stream()
                               .anyMatch(lang -> lang.startsWith("J"));
System.out.println("是否有以J开头的语言:" + hasJLanguage); // true

// 检查是否有长度大于5的语言
boolean hasLongNameLanguage = languages.stream()
                                     .anyMatch(lang -> lang.length() > 5);
System.out.println("是否有名称长度大于5的语言:" + hasLongNameLanguage); // true

// 检查是否所有语言都包含字母a
boolean allContainA = languages.stream()
                             .allMatch(lang -> lang.contains("a"));
System.out.println("是否所有语言都包含字母a:" + allContainA); // false

// 检查是否没有长度大于5的语言
boolean noneLongNameLanguage = languages.stream()
                                       .noneMatch(lang -> lang.length() > 5);
System.out.println("是否没有名称长度大于5的语言:" + noneLongNameLanguage); // false

4.4 归约操作(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

// 计算所有数字的乘积
int product = numbers.stream()
                    .reduce(1, (a, b) -> a * b);
System.out.println("所有数字的乘积:" + product); // 120

// 使用自定义归约函数
Optional<String> concatenated = languages.stream()
                                       .reduce((a, b) -> a + ", " + b);
System.out.println("所有语言的连接:" + concatenated.orElse("没有语言")); // Java, Python, JavaScript, C#, C++, Kotlin

4.5 统计操作(count)

count方法用于统计流中的元素数量。

List<String> languages = Arrays.asList("Java", "Python", "JavaScript", "C#", "C++", "Kotlin");

// 统计以J开头的语言数量
long jLanguageCount = languages.stream()
                               .filter(lang -> lang.startsWith("J"))
                               .count();
System.out.println("以J开头的语言数量:" + jLanguageCount); // 2

// 统计长度大于5的语言数量
long longNameLanguageCount = languages.stream()
                                     .filter(lang -> lang.length() > 5)
                                     .count();
System.out.println("名称长度大于5的语言数量:" + longNameLanguageCount); // 3

// 统计包含字母a的语言数量
long containALanguageCount = languages.stream()
                                     .filter(lang -> lang.contains("a"))
                                     .count();
System.out.println("包含字母a的语言数量:" + containALanguageCount); // 4

4.6 实际应用示例

下面通过一个综合示例,展示多种终端操作的组合使用:

// 一个学生类
class Student {
    private String name;
    private int age;
    private String gender;
    private List<String> courses;
    
    public Student(String name, int age, String gender, List<String> courses) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.courses = courses;
    }
    
    // 省略getter、setter
    
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getGender() { return gender; }
    public List<String> getCourses() { return courses; }
    
    @Override
    public String toString() {
        return name + "(" + age + ", " + gender + ")";
    }
}

// 学生数据
List<Student> students = Arrays.asList(
    new Student("Alice", 20, "Female", Arrays.asList("Math", "Physics", "Chemistry")),
    new Student("Bob", 22, "Male", Arrays.asList("Computer Science", "Math")),
    new Student("Charlie", 19, "Male", Arrays.asList("Physics", "Biology")),
    new Student("Diana", 21, "Female", Arrays.asList("Computer Science", "Literature")),
    new Student("Eve", 20, "Female", Arrays.asList("Math", "Economics"))
);

// 获取所有女生选修的不重复课程,按字母排序
List<String> femaleStudentCourses = students.stream()
                                         .filter(s -> s.getGender().equals("Female"))
                                         .flatMap(s -> s.getCourses().stream())
                                         .distinct()
                                         .sorted()
                                         .collect(Collectors.toList());
System.out.println("女生选修的课程(去重排序):" + femaleStudentCourses);
// [Chemistry, Computer Science, Economics, Literature, Math, Physics]

// 获取年龄大于20岁的学生,按年龄降序,再按姓名升序
List<Student> sortedStudents = students.stream()
                                    .filter(s -> s.getAge() > 20)
                                    .sorted(Comparator.comparing(Student::getAge).reversed()
                                           .thenComparing(Student::getName))
                                    .collect(Collectors.toList());
System.out.println("年龄大于20岁的学生(按年龄降序,再按姓名升序):" + sortedStudents);
// [Bob(22, Male), Diana(21, Female)]

// 计算所有学生选修的课程总数(包含重复)
long totalCourseCount = students.stream()
                             .flatMap(s -> s.getCourses().stream())
                             .count();
System.out.println("所有学生选修的课程总数(包含重复):" + totalCourseCount); // 10

// 查找20岁学生的课程,并统计每门课程的选修人数
Map<String, Long> courseCountMap = students.stream()
                                       .filter(s -> s.getAge() == 20)
                                       .flatMap(s -> s.getCourses().stream())
                                       .collect(Collectors.groupingBy(
                                           course -> course,
                                           Collectors.counting()
                                       ));
System.out.println("20岁学生中各课程的选修人数:" + courseCountMap);
// {Economics=1, Physics=1, Chemistry=1, Math=2}

5. 并行流

并行流(Parallel Stream)是Stream流的一种特殊形式,它允许将流操作并行化,从而提高性能。并行流通过parallel()方法创建,它会将流中的元素分配到多个线程中进行处理。

5.1 并行流的优点

  1. 提高性能:并行流可以利用多核架构实现并行处理,从而提高性能。
  2. 简化代码:并行流的使用可以让开发者更加专注于业务逻辑,而不需要关心线程安全和并发问题。

5.2 并行流的缺点

  1. 复杂性:并行流的使用会增加代码的复杂性,需要考虑线程安全和并发问题。
  2. 内存消耗:并行流会消耗更多的内存,因为需要为每个线程分配独立的内存空间。

5.3 并行流的实际应用

并行流通常用于处理大量数据或需要高性能的场景。例如,在处理大数据集时,并行流可以显著提高处理速度。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// 使用并行流
numbers.parallelStream()
       .filter(n -> n % 2 == 0)
       .map(n -> n * 2)
       .forEach(System.out::println);

6. Stream流在实际开发中的应用

Stream流在实际开发中有很多应用场景,下面介绍几个常见的应用场景。

6.1 数据过滤和转换

Stream流可以用于对数据进行过滤和转换。例如,从一个列表中找出所有大于3的偶数,并将这些数字乘以2后输出。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

numbers.stream()
       .filter(n -> n > 3)         // 过滤大于3的数
       .filter(n -> n % 2 == 0)    // 过滤偶数
       .map(n -> n * 2)            // 将每个数乘以2
       .forEach(System.out::println); // 输出结果

6.2 数据收集和归约

Stream流可以用于对数据进行收集和归约。例如,将一个列表中的所有元素收集到一个新的列表中。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> squaredNumbers = numbers.stream()
                                     .map(n -> n * n)
                                     .collect(Collectors.toList());
System.out.println("平方后的数字:" + squaredNumbers); // [1, 4, 9, 16, 25]

6.3 数据分组和统计

Stream流可以用于对数据进行分组和统计。例如,将一个列表中的学生按性别分组,并统计每个组的学生数量。

List<Student> students = Arrays.asList(
    new Student("Alice", 20, "Female", Arrays.asList("Math", "Physics", "Chemistry")),
    new Student("Bob", 22, "Male", Arrays.asList("Computer Science", "Math")),
    new Student("Charlie", 19, "Male", Arrays.asList("Physics", "Biology")),
    new Student("Diana", 21, "Female", Arrays.asList("Computer Science", "Literature")),
    new Student("Eve", 20, "Female", Arrays.asList("Math", "Economics"))
);

Map<String, Long> genderCountMap = students.stream()
                                         .collect(Collectors.groupingBy(
                                             Student::getGender,
                                             Collectors.counting()
                                         ));
System.out.println("每个性别的学生数量:" + genderCountMap); // {Female=3, Male=2}

7. 常见问题与最佳实践

7.1 延迟执行

Stream流的操作是延迟执行的,这意味着它们直到需要结果时才会计算。因此,在使用Stream流时需要注意避免不必要的中间操作,以提高性能。

7.2 并行流的使用

并行流可以显著提高性能,但需要注意线程安全和并发问题。在处理大量数据或需要高性能的场景时,可以考虑使用并行流。

7.3 实际应用示例

下面是一个综合示例,展示了如何使用不同方式创建Stream流并进行简单操作:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamCreationExample {
    public static void main(String[] args) {
        // 1. 从集合创建Stream流
        List<String> languages = Arrays.asList("Java", "Python", "C++", "JavaScript");
        System.out.println("从List创建Stream流:");
        languages.stream()
                .filter(lang -> lang.startsWith("J"))
                .forEach(System.out::println);
                
        // 2. 从数组创建Stream流
        String[] languageArray = {"Java", "Python", "C++", "JavaScript"};
        System.out.println("\n从数组创建Stream流:");
        Arrays.stream(languageArray)
              .map(String::toUpperCase)
              .forEach(System.out::println);
              
        // 3. 创建数值Stream流
        System.out.println("\n创建数值Stream流:");
        IntStream.rangeClosed(1, 5)
                .map(n -> n * n)
                .forEach(System.out::println);
                
        // 4. 创建无限Stream流并限制数量
        System.out.println("\n创建无限Stream流(斐波那契数列的前10个数):");
        Stream.iterate(new int[]{0, 1}, arr -> new int[]{arr[1], arr[0] + arr[1]})
              .limit(10)
              .map(arr -> arr[0])
              .forEach(System.out::println);
              
        // 5. 从文件创建Stream流
        System.out.println("\n从文件创建Stream流:");
        try {
            Files.lines(Paths.get("example.txt"))
                 .limit(3) // 只读取前3行
                 .forEach(System.out::println);
        } catch (IOException e) {
            System.out.println("文件不存在或读取错误: " + e.getMessage());
        }
        
        // 6. 创建空Stream流
        System.out.println("\n创建空Stream流:");
        long count = Stream.empty().count();
        System.out.println("空Stream流的元素数量: " + count);
        
        // 7. 合并Stream流
        System.out.println("\n合并Stream流:");
        Stream<String> frontend = Stream.of("HTML", "CSS", "JavaScript");
        Stream<String> backend = Stream.of("Java", "Python", "Node.js");
        Stream.concat(frontend, backend)
              .sorted()
              .forEach(lang -> System.out.print(lang + " "));
    }
}

通过上面的例子,我们可以看到创建Stream流的多种方式以及简单的操作示例。在实际应用中,可以根据数据源和需求选择合适的方式创建Stream流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈凯哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值