Java中,stream()方法使用汇总

目标

  • 通过示例学习,掌握Javastream()方法的实践应用

在Java中,stream()方法用于将集合(如List、Set等)或数组转换为Stream流对象,以便进行各种流式操作。流(Stream)提供了一种高效且声明式的方式来处理数据集合。使用流,你可以对集合中的元素执行一系列复杂的操作,如过滤、映射、排序、聚合等,而无需编写繁琐的循环代码。

1. 过滤(Filter)

使用filter()方法可以根据条件筛选流中的元素。

import org.junit.Test;
import static org.junit.Assert.*;
import java.util.stream.Stream;
import java.util.function.Predicate;

public class StreamTest {
    
    @Test
    public void testFilter() {
        // Create a stream of integers
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        
        // Define a predicate to filter even numbers
        Predicate<Integer> isEven = n -> n % 2 == 0;
        
        // Filter the stream using the predicate
        Stream<Integer> filteredStream = stream.filter(isEven);
        
        // Convert the stream to an array for assertion
        Integer[] filteredArray = filteredStream.toArray(Integer[]::new);
        
        // Assert that the filtered array contains only even numbers
        assertArrayEquals(new Integer[]{2, 4}, filteredArray);
    }
}

2. 映射(Map)

使用map()方法可以将流中的元素转换为其他对象或类型。

List<String> strings = Arrays.asList("apple", "banana", "cherry");  
List<Integer> stringLengths = strings.stream()  
    .map(String::length)  
    .collect(Collectors.toList());

3. 排序(Sort)

使用sorted()方法可以对流中的元素进行排序。

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5);  
List<Integer> sortedNumbers = numbers.stream()  
    .sorted()  
    .collect(Collectors.toList());

4. 聚合(Reduce)

使用reduce()方法可以将流中的元素聚合为一个单独的值。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);  
Optional<Integer> sum = numbers.stream()  
    .reduce((a, b) -> a + b);  
int total = sum.orElseThrow(); // 或者 sum.get() 如果确定流不为空

5. 查找(Find)

使用findAny()findFirst()方法可以从流中找到一个元素。

List<String> strings = Arrays.asList("apple", "banana", "cherry");  
Optional<String> firstString = strings.stream()  
    .findFirst();

6. 分组(Grouping)

使用collect(Collectors.groupingBy(...))方法可以根据某个条件对流中的元素进行分组。

List<Person> people = Arrays.asList(  
    new Person("Alice", 25),  
    new Person("Bob", 30),  
    new Person("Charlie", 25),  
    new Person("David", 35)  
);  
  
Map<Integer, List<Person>> peopleByAge = people.stream()  
    .collect(Collectors.groupingBy(Person::getAge));

7. 连接(Joining)

使用collect(Collectors.joining(...))方法可以将流中的字符串元素连接成一个单独的字符串。

List<String> words = Arrays.asList("Hello", "world", "!");  
String sentence = words.stream()  
    .collect(Collectors.joining(" "));

8. 匹配(Matching)

使用anyMatch(), allMatch(), 和 noneMatch() 方法可以对流中的元素进行匹配操作,以检查是否至少有一个、所有或没有元素满足某个条件。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);  
boolean hasEven = numbers.stream()  
    .anyMatch(n -> n % 2 == 0); // 至少有一个偶数

9. 数值操作(Numeric Operations)

对于数值流(如IntStream, LongStream, DoubleStream),可以使用sum(), average(), max(), min()等方法进行数值操作。

IntStream numbers = IntStream.of(1, 2, 3, 4, 5);  
int sum = numbers.sum();  
OptionalDouble average = numbers.average();

10. 扁平化(Flattening)

使用flatMap()方法可以将流中的元素转换为其他流,并将这些流中的所有元素合并成一个流。

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

import java.util.stream.Stream;
import java.util.function.Function;

public class StreamTest {

    @Test
    public void testFlatMap() {
        Stream<Integer> stream = Stream.of(1, 2, 3);
        Function<Integer, Stream<String>> mapper = i -> Stream.of("a", "b", "c");

        Stream<String> result = stream.flatMap(mapper);

        assertNotNull(result);
        assertEquals(9, result.count());
        assertTrue(result.anyMatch("a"::equals));
        assertTrue(result.anyMatch("b"::equals));
        assertTrue(result.anyMatch("c"::equals));
    }

    @Test
    public void testFlatMapWithNullMapper() {
        Stream<Integer> stream = Stream.of(1, 2, 3);
        Function<Integer, Stream<String>> mapper = null;

        Exception exception = assertThrows(NullPointerException.class, () -> {
            stream.flatMap(mapper);
        });

        assertEquals("mapper cannot be null", exception.getMessage());
    }

    @Test
    public void testFlatMapWithEmptyStream() {
        Stream<Integer> stream = Stream.empty();
        Function<Integer, Stream<String>> mapper = i -> Stream.of("a", "b", "c");

        Stream<String> result = stream.flatMap(mapper);

        assertNotNull(result);
        assertTrue(result.isEmpty());
    }
}

11、 List转map 键(key)不重复

在Java中,将List转换为Map通常涉及到将List中的元素按照某种规则映射到Map的键值对上。这通常涉及到List中的对象具有可以作为键(key)和值(value)的属性。以下是一个简单的示例,说明如何将一个包含自定义对象的List转换为Map。

假设我们有一个Person类,它有两个属性:nameage

public class Person {  
    private String name;  
    private int age;  
  
    // 构造器、getter和setter方法省略...  
}

现在,假设我们有一个Person对象的List,并且我们想要将这个List转换为一个Map,其中键是name,值是对应的Person对象。我们可以使用Java 8的Stream API来实现这个转换:

import java.util.List;  
import java.util.Map;  
import java.util.stream.Collectors;  
  
public class Main {  
    public static void main(String[] args) {  
        List<Person> people = List.of(  
            new Person("Alice", 25),  
            new Person("Bob", 30),  
            new Person("Charlie", 35)  
        );  
  
        Map<String, Person> peopleMap = people.stream()  
            .collect(Collectors.toMap(Person::getName, person -> person));  
  
        // 打印Map的内容  
        peopleMap.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));  
    }  
}

12、List转map 键(key)重复

当使用Java 8的Stream API将List转换为Map,并需要自定义处理重复键时,你可以利用Collectors.toMap方法,并传入一个自定义的合并函数来处理键冲突。下面是一个示例,演示了如何使用Stream API以及自定义的合并逻辑来处理重复的键:

假设我们有一个员工列表,每个员工都有一个ID和姓名,我们希望将这些员工转换为一个Map,其中键是员工的ID,值是员工的姓名。如果遇到重复的ID,我们将所有具有相同ID的员工的姓名连接成一个字符串。

import java.util.*;  
import java.util.function.BinaryOperator;  
import java.util.stream.Collectors;  
  
public class ListStreamToMapExample {  
    public static void main(String[] args) {  
        // 假设我们有一个员工列表,其中可能包含重复的员工ID  
        List<Employee> employees = Arrays.asList(  
                new Employee("1", "Alice"),  
                new Employee("2", "Bob"),  
                new Employee("1", "Charlie"), // 重复的ID  
                new Employee("3", "David")  
        );  
  
        // 我们想要将这些员工转换为一个Map,其中键是员工的ID,值是员工的姓名  
        // 如果遇到重复的ID,我们将所有具有相同ID的员工的姓名连接成一个字符串  
        Map<String, String> employeeMap = employees.stream()  
                .collect(Collectors.toMap(  
                        Employee::getId, // 键映射函数  
                        Employee::getName, // 值映射函数  
                        (name1, name2) -> name1 + ", " + name2 // 合并函数,处理重复的键  
                ));  
  
        // 打印结果以验证  
        employeeMap.forEach((id, name) -> System.out.println("ID: " + id + ", Name: " + name));  
    }  
  
    // 员工类,包含ID和姓名  
    static class Employee {  
        private String id;  
        private String name;  
  
        public Employee(String id, String name) {  
            this.id = id;  
            this.name = name;  
        }  
  
        public String getId() {  
            return id;  
        }  
  
        public String getName() {  
            return name;  
        }  
  
        // 根据需要重写equals和hashCode方法  
        @Override  
        public boolean equals(Object o) {  
            if (this == o) return true;  
            if (o == null || getClass() != o.getClass()) return false;  
            Employee employee = (Employee) o;  
            return Objects.equals(id, employee.id);  
        }  
  
        @Override  
        public int hashCode() {  
            return Objects.hash(id);  
        }  
    }  
}

在这个例子中,Collectors.toMap接收三个参数:

  1. 键映射函数(Employee::getId),它定义了如何从流中的元素提取键。
  2. 值映射函数(Employee::getName),它定义了如何从流中的元素提取值。
  3. 合并函数((name1, name2) -> name1 + ", " + name2),它定义了当遇到重复的键时如何合并值。在这个例子中,我们将两个姓名连接成一个字符串,用逗号和空格分隔。

运行这段代码后,你将得到一个Map,其中重复的ID对应的值是所有具有该ID的员工的姓名连接而成的字符串。例如,ID为"1"的键对应的值将是"Alice, Charlie"。