看了月神的文章,放在我这里,方便复习浏览

一、Stream 流概述

以前写集合处理:要写很多 for 循环、代码冗长

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

List<Integer> res = new ArrayList<>();
for(int x : list){
    if(x % 2 == 0){
        res.add(x * 2);
    }
}

Stream 的目标

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

List<Integer> res = list.stream()
        .filter(x -> x % 2 == 0)   // 过滤偶数
        .map(x -> x * 2)           // 每个数 *2
        .collect(Collectors.toList());

效果:

//filter(过滤)
list.stream()
    .filter(x -> x > 3)
//map(转换)
.map(x -> x * 2)
//sorted(排序)
.sorted()
//forEach(遍历)
list.stream().forEach(System.out::println);
//collect(收集结果)
.collect(Collectors.toList())
//reduce(累加/汇总
int sum = list.stream()
        .reduce(0, (a, b) -> a + b);

Stream 流是 Java 8 中引入的一个重要新特性,它允许我们以声明式的方式处理集合数据。Stream 流就像是一个流水线,我们可以在这个流水线上对数据进行一系列的操作,比如过滤、映射、排序等,最终得到我们想要的结果。

与传统的集合遍历方式相比,Stream 流具有代码简洁、可读性高、易于并行处理等优点。它不会改变原始集合的数据,所有的操作都是在流上进行的,并且只有在执行终止操作时,中间操作才会真正执行,这就是所谓的 “惰性求值”。

二、Stream 流的创建

1. 通过集合创建

在 Java 8 中,Collection 接口新增了 stream () 方法,用于返回一个顺序流;还新增了 parallelStream () 方法,用于返回一个并行流。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamCreateDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        
        // 获取顺序流
        Stream<String> stream = list.stream();
        
        // 获取并行流
        Stream<String> parallelStream = list.parallelStream();
    }
}

这个示例很简单,我们先创建了一个存储字符串的 List 集合,然后分别通过 stream () 和 parallelStream () 方法获取了顺序流和并行流。顺序流是单线程处理数据,并行流则是多线程处理数据,在处理大量数据时,并行流可以提高处理效率。

2. 通过数组创建

Arrays 类提供了 stream () 方法,可以将数组转换为 Stream 流。

import java.util.Arrays;
import java.util.stream.Stream;

public class StreamCreateDemo2 {
    public static void main(String[] args) {
        String[] strArray = {"a", "b", "c"};
        Stream<String> stream = Arrays.stream(strArray);
    }
}

这里我们创建了一个字符串数组,然后使用 Arrays.stream () 方法将其转换为了 Stream 流,非常直观易懂。

3. 通过 Stream 的 of () 方法创建

Stream 类的 of () 方法可以直接传入元素来创建 Stream 流。例如,Stream.of(“苹果”, “香蕉”, “橘子”); 这样就创建了一个包含这三个字符串元素的Stream流,无需先构建集合或数组,特别适合少量元素的快速创建场景。

import java.util.stream.Stream;

public class StreamCreateDemo3 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("苹果", "香蕉", "橘子");
    }
}

在这个示例中,我们直接将 “苹果”、“香蕉”、“橘子” 这几个元素传入 Stream.of () 方法,就创建了一个包含这些元素的 Stream 流,操作非常简便。

三、Stream 流的中间操作

中间操作是指对 Stream 流进行处理后,返回一个新的 Stream 流的操作。中间操作不会立即执行,只有当终止操作执行时,中间操作才会真正执行。常见的中间操作有过滤、映射、排序等。

1. 过滤(filter)

filter () 方法用于过滤出满足指定条件的元素,它接收一个 Predicate 函数式接口作为参数。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamFilterDemo {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);
        numbers.add(5);
        
        // 过滤出大于3的数字
        Stream<Integer> filterStream = numbers.stream().filter(n -> n > 3);
        
        // 终止操作:输出结果
        filterStream.forEach(System.out::println); // 输出4、5
    }
}

在这个示例中,我们创建了一个包含 1 到 5 的整数集合,然后通过 filter () 方法过滤出大于 3 的数字。这里的n -> n > 3是一个 Lambda 表达式,作为 Predicate 接口的实现,用于判断元素是否满足条件。最后通过 forEach () 这个终止操作输出结果,可以看到只输出了 4 和 5。

2. 映射(map)

map () 方法用于将流中的元素映射到另一个元素,可以将一种类型的流转换为另一种类型的流,它接收一个 Function 函数式接口作为参数。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class StreamMapDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("张三");
        names.add("李四");
        names.add("王五");
        
        // 将名字转换为名字的长度
        Stream<Integer> nameLengthStream = names.stream().map(name -> name.length());
        
        // 终止操作:输出结果
        nameLengthStream.forEach(System.out::println); // 输出2、2、2
    }
}

我们有一个存储名字的集合,通过 map () 方法将每个名字映射为它的长度,这里的name -> name.length()是一个 Lambda 表达式,实现了 Function 接口,将 String 类型的名字转换为 Integer 类型的长度。最后输出的结果都是 2,因为这三个名字都是两个字。

3. 排序(sorted)

sorted () 方法用于对 Stream 流中的元素进行排序。它有两种形式,一种是无参的,用于对实现了 Comparable 接口的元素进行自然排序;另一种是有参的,接收一个 Comparator 接口作为参数,用于自定义排序规则。

import java.util.ArrayList;
import java.util.List;
import java.util.Comparator;

public class StreamSortedDemo {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(3);
        numbers.add(1);
        numbers.add(2);
        
        // 自然排序
        System.out.println("自然排序结果:");
        numbers.stream().sorted().forEach(System.out::println); // 输出1、2、3
        
        List<String> words = new ArrayList<>();
        words.add("banana");
        words.add("apple");
        words.add("orange");
        
        // 自定义排序(按字符串长度排序)
        System.out.println("按字符串长度排序结果:");
        words.stream().sorted(Comparator.comparingInt(String::length)).forEach(System.out::println); // 输出apple、orange、banana
    }
}

在第一个示例中,我们对整数集合进行自然排序,sorted () 方法无参,直接按照整数的自然顺序排序,结果是 1、2、3。在第二个示例中,我们对字符串集合按照字符串长度进行排序,通过Comparator.comparingInt(String::length)自定义了排序规则,apple 长度为 5,orange 长度为 6,banana 长度为 6,所以输出结果为 apple、orange、banana。

四、Stream 流的终止操作

终止操作会触发流的处理,并产生一个结果或副作用,一旦执行了终止操作,流就不能再被使用了。常见的终止操作有 forEach、count、collect 等。

1. forEach

forEach () 方法用于遍历流中的元素,对每个元素执行指定的操作,它接收一个 Consumer 函数式接口作为参数。

import java.util.ArrayList;
import java.util.List;

public class StreamForEachDemo {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("苹果");
        fruits.add("香蕉");
        fruits.add("橘子");
        
        // 遍历输出每个水果
        fruits.stream().forEach(fruit -> System.out.println(fruit));
        // 输出:苹果、香蕉、橘子
    }
}

这个示例很简单,我们创建了一个水果集合,通过 forEach () 方法遍历流中的每个水果,并将其输出。fruit -> System.out.println(fruit)这个 Lambda 表达式实现了 Consumer 接口,用于消费流中的元素。

2. count

count () 方法用于返回流中元素的个数,返回值是一个 long 类型。

import java.util.ArrayList;
import java.util.List;

public class StreamCountDemo {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);
        numbers.add(5);
        
        // 统计元素个数
        long count = numbers.stream().count();
        System.out.println("元素个数为:" + count); // 输出5
    }
}

我们创建了一个包含 5 个整数的集合,通过 count () 方法统计流中元素的个数,最后输出结果为 5,非常直观地展示了 count () 方法的用法。

3. collect

collect () 方法用于将流中的元素收集到一个集合或其他数据结构中,它接收一个 Collector 接口的实现作为参数。Java 8 的 Collectors 工具类提供了很多常用的 Collector 实现。

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class StreamCollectDemo {
    public static void main(String[] args) {
        List<String> languages = new ArrayList<>();
        languages.add("Java");
        languages.add("Python");
        languages.add("Java");
        languages.add("C++");
        
        // 将流收集到List集合中
        List<String> list = languages.stream().collect(Collectors.toList());
        System.out.println("List集合:" + list); // 输出[Java, Python, Java, C++]
        
        // 将流收集到Set集合中(去重)
        Set<String> set = languages.stream().collect(Collectors.toSet());
        System.out.println("Set集合:" + set); // 输出[Java, Python, C++]
    }
}

我们有一个存储编程语言的集合,其中 “Java” 出现了两次。通过 collect (Collectors.toList ()) 将流收集到 List 集合中,得到的 List 包含了所有元素,包括重复的 “Java”。而通过 collect (Collectors.toSet ()) 将流收集到 Set 集合中,由于 Set 集合不允许重复元素,所以得到的 Set 集合中 “Java” 只出现了一次。

五、综合示例

下面我们通过一个综合示例来运用前面所学的 Stream 流的知识,实现对一个员工列表的处理。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

// 员工类
class Employee {
    private String name;
    private int age;
    private double salary;

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public String toString() {
        return "Employee{name='" + name + "', age=" + age + ", salary=" + salary + '}';
    }
}
public class StreamComprehensiveDemo {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee("张三", 25, 5000));
        employees.add(new Employee("李四", 30, 7000));
        employees.add(new Employee("王五", 28, 6000));
        employees.add(new Employee("赵六", 35, 9000));
        employees.add(new Employee("孙七", 22, 4000));
        
        // 需求:筛选出年龄大于25岁的员工,将他们的工资增加1000,然后按工资从高到低排序,最后收集到List集合中
        List<Employee> result = employees.stream()
                .filter(employee->employee.getAge() > 25)   // 筛选年龄大于25岁的员工
                .map(employee-> new Employee(employee.getName(), employee.getAge(), employee.getSalary()+1000))  // 工资增加1000
                .sorted(Comparator.comparingDouble(Employee::getSalary).reversed())   // 按工资从高到低排序
                //.sorted((e1, e2) -> Double.compare(e2.getSalary(), e1.getSalary())) // 按工资从高到低排序
                .toList();  // 收集到List集合中
        
        // 输出结果
        result.forEach(System.out::println);
        // 输出:
        // Employee{name='赵六', age=35, salary=10000.0}
        // Employee{name='李四', age=30, salary=8000.0}
        // Employee{name='王五', age=28, salary=7000.0}
    }
}

在这个综合示例中,我们首先创建了一个员工列表,每个员工有姓名、年龄和工资属性。然后我们通过一系列的 Stream 流操作来满足需求:首先使用 filter () 方法筛选出年龄大于 25 岁的员工;接着使用 map () 方法为这些员工增加 1000 工资;再使用 sorted () 方法按照工资从高到低进行排序;最后使用 collect () 方法将结果收集到 List 集合中。通过 forEach () 方法输出结果,可以看到符合条件的员工信息按照我们的要求进行了处理。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐