Java进阶-Java Stream API详解与使用

60 阅读9分钟

第一章:引言

1.1 Java Stream API简介

Java Stream API是Java 8引入的一个强大的特性,它提供了一种声明式的方式来处理集合数据。Stream API允许以一种高效且易于阅读的方式对集合进行操作,如筛选、排序、聚合等。

1.2 Stream API在Java编程中的重要性

Stream API不仅提高了代码的可读性和简洁性,还因其内部实现的优化,提升了性能。它支持函数式编程范式,使得并行处理集合数据变得更加简单。

示例代码:Stream API的基本使用

下面是一个简单的示例,展示如何在Java中使用Stream API:

import java.util.Arrays;

public class StreamAPIIntroduction {
    public static void main(String[] args) {
        // 创建一个整数数组
        int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        // 将数组转换为Stream
        java.util.stream.IntStream stream = Arrays.stream(numbers);

        // 使用Stream API进行操作
        // 筛选出偶数
        java.util.stream.IntStream evenNumbers = stream.filter(n -> n % 2 == 0);

        // 将筛选后的流转换为数组并打印
        evenNumbers.toArray();
        System.out.println(Arrays.toString(evenNumbers.toArray()));
    }
}

这段代码演示了如何将一个数组转换为Stream,使用filter中间操作筛选出偶数,并将结果转换回数组。

结语

在本章中,我们对Java Stream API进行了初步的介绍,并强调了它在现代Java编程中的重要性。通过示例代码,我们学习了如何使用Stream API进行基本的数据操作。

第二章:Stream API基础

2.1 流的概念和特性

流(Stream)是一种特殊的迭代器,它对集合进行迭代操作。与传统迭代器相比,流提供了懒加载特性,即在需要时才会进行计算。流是一次性的,一旦遍历完成,就不能再次使用。

2.2 创建流的多种方式

流可以从多种数据源创建,包括数组、集合、I/O通道等。以下是几种常见的创建流的方法:

  • 从数组创建流:使用Arrays.stream()方法。
  • 从集合创建流:使用集合的stream()方法。
  • 从值创建流:使用Stream.of()方法。

示例代码:创建流的不同方式

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

public class StreamAPIBasics {
    public static void main(String[] args) {
        // 从数组创建流
        String[] fruitsArray = {"Apple", "Banana", "Cherry"};
        Stream<String> fruitStream = Arrays.stream(fruitsArray);

        // 从集合创建流
        List<String> fruitsList = Arrays.asList(fruitsArray);
        Stream<String> listStream = fruitsList.stream();

        // 从值创建流
        Stream<String> valueStream = Stream.of("Apple", "Banana", "Cherry");

        // 打印流中的元素
        fruitStream.forEach(fruit -> System.out.println(fruit));
        listStream.forEach(fruit -> System.out.println(fruit));
        valueStream.forEach(fruit -> System.out.println(fruit));
    }
}

这段代码演示了如何从数组、集合和值创建流,并遍历流中的元素。

结语

在本章中,我们学习了流的基本概念和特性,以及如何从不同的数据源创建流。示例代码展示了流的创建和基本使用。

第三章:中间操作

中间操作是Stream API中对流进行处理的方法,它们不会立即产生结果,而是在流上创建了一个新的流。中间操作可以链接起来,形成一条流的流水线。

3.1 过滤(filter

过滤操作可以筛选出流中满足特定条件的元素。

3.2 映射(map

映射操作可以对流中的每个元素应用一个函数,将它们转换成另一种形式或类型。

3.3 排序(sorted

排序操作可以将流中的元素按照自然顺序或指定的顺序进行排序。

3.4 其他中间操作

  • distinct():去除流中的重复元素。
  • limit(n):保留流中的前n个元素。
  • skip(n):跳过流中的前n个元素。

示例代码:中间操作的使用

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

public class StreamIntermediateOperations {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 使用filter进行过滤
        List<Integer> filteredNumbers = numbers.stream()
                .filter(n -> n % 2 != 0) // 筛选出奇数
                .collect(Collectors.toList());
        System.out.println("Filtered numbers: " + filteredNumbers);

        // 使用map进行映射
        List<Integer> squaredNumbers = numbers.stream()
                .map(n -> n * n) // 对每个元素求平方
                .collect(Collectors.toList());
        System.out.println("Squared numbers: " + squaredNumbers);

        // 使用sorted进行排序
        List<Integer> sortedNumbers = numbers.stream()
                .sorted() // 默认按照自然顺序排序
                .collect(Collectors.toList());
        System.out.println("Sorted numbers: " + sortedNumbers);

        // 使用distinct去除重复
        List<Integer> distinctNumbers = numbers.stream()
                .distinct()
                .collect(Collectors.toList());
        System.out.println("Distinct numbers: " + distinctNumbers);

        // 使用limit和skip
        List<Integer> limitedNumbers = numbers.stream()
                .limit(5) // 保留前5个元素
                .collect(Collectors.toList());
        System.out.println("Limited numbers: " + limitedNumbers);
        List<Integer> skippedNumbers = numbers.stream()
                .skip(5) // 跳过前5个元素
                .collect(Collectors.toList());
        System.out.println("Skipped numbers: " + skippedNumbers);
    }
}

这段代码演示了如何使用过滤、映射、排序、去除重复、限制和跳过元素等中间操作。

第四章:终止操作

终止操作是Stream API中最终产生结果的操作。在执行终止操作之前,中间操作构建的流水线不会执行任何实际操作,这种特性被称为惰性求值。

4.1 查找(find系列方法)

  • findFirst():返回流中第一个元素的Optional。
  • findAny():返回流中任意一个元素的Optional,对于并行流特别有用。

4.2 计数(count

计数操作返回流中元素的数量。

4.3 规约(reduce

规约操作可以将流中的元素通过一个累积器函数(如求和、连接字符串等)归约为一个单一的值。

4.4 其他终止操作

  • forEach():对流中的每个元素执行操作。
  • collect():将流中的元素收集到一个新集合中。

示例代码:终止操作的使用

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class StreamTerminationOperations {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date");

        // 使用findFirst找到第一个元素
        Optional<String> firstFruit = fruits.stream().findFirst();
        firstFruit.ifPresent(System.out::println);

        // 使用findAny找到任意一个元素
        Optional<String> anyFruit = fruits.parallelStream().findAny();
        anyFruit.ifPresent(System.out::println);

        // 使用count计数
        long fruitCount = fruits.stream().count();
        System.out.println("Fruit count: " + fruitCount);

        // 使用reduce进行求和
        int sum = Arrays.stream(new int[]{1, 2, 3, 4, 5}).reduce(0, Integer::sum);
        System.out.println("Sum of numbers: " + sum);

        // 使用forEach对每个元素执行操作
        fruits.stream().forEach(System.out::println);

        // 使用collect收集元素到新列表
        List<String> collectedFruits = fruits.stream().collect(Collectors.toList());
        System.out.println("Collected fruits: " + collectedFruits);
    }
}

这段代码演示了如何使用查找、计数、规约和收集等终止操作。

第五章:并行流

5.1 并行流的概念和优势

并行流是利用多核处理器的计算能力,对数据集进行并行处理的流。它可以显著提高性能,特别是对于大数据集和计算密集型任务。

5.2 如何创建并行流

并行流可以通过在流上调用parallelStream()方法来创建。

5.3 并行流的性能考量

虽然并行流可以提高性能,但也有一些因素需要考虑:

  • 任务的粒度:小任务可能不值得并行化,因为线程创建和同步的开销可能超过其带来的性能提升。
  • 线程安全:并行流中的操作必须是线程安全的,否则可能会导致不可预测的结果。

示例代码:并行流的使用

import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

public class ParallelStreamExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        // 创建并行流
        long sum = Arrays.stream(numbers).parallel().reduce(0, Integer::sum);

        System.out.println("Sum of numbers using parallel stream: " + sum);

        // 并行流中的forEach
        AtomicInteger result = new AtomicInteger(0);
        numbers.lengthTimes(10).parallelStream().forEach(i -> result.updateAndGet(v -> v + i));

        System.out.println("Result of parallel forEach: " + result);
    }
    
    // 模拟numbers.length * 10次操作的数组
    private static int[] numbersLengthTimes(int times) {
        int[] array = new int[times * numbers.length];
        for (int i = 0; i < array.length; i++) {
            array[i] = i % numbers.length;
        }
        return array;
    }
}

这段代码演示了如何创建并行流,以及如何在并行流上执行reduceforEach操作。

第六章:Stream API与Lambda表达式

6.1 Lambda表达式在Stream API中的应用

Lambda表达式为Stream API提供了一种简洁的方式来表达中间操作和终止操作中的函数逻辑。

6.2 使用Lambda表达式简化代码

Lambda表达式可以简化匿名内部类的使用,使代码更加简洁和易于理解。

示例代码:Lambda表达式与Stream API的结合使用

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class StreamAndLambdaExample {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date");

        // 使用Lambda表达式进行过滤
        List<String> filteredFruits = fruits.stream()
                .filter(fruit -> fruit.startsWith("B"))
                .collect(Collectors.toList());
        System.out.println("Filtered fruits: " + filteredFruits);

        // 使用Lambda表达式进行映射
        List<Integer> fruitLengths = fruits.stream()
                .map(String::length)
                .collect(Collectors.toList());
        System.out.println("Fruit lengths: " + fruitLengths);

        // 使用Lambda表达式进行查找
        Optional<String> longestFruit = fruits.stream()
                .max((fruit1, fruit2) -> Integer.compare(fruit1.length(), fruit2.length()));
        longestFruit.ifPresent(System.out::println);

        // 使用Lambda表达式进行规约
        int totalLength = fruits.stream()
                .reduce(0, (sum, fruit) -> sum + fruit.length(), Integer::sum);
        System.out.println("Total length of fruit names: " + totalLength);
    }
}

这段代码演示了如何在Stream API中使用Lambda表达式进行过滤、映射、查找和规约操作。

第七章:高级特性

7.1 流的收集(collect

收集操作是终止操作的一种,它可以将流中的元素累积到一个结果容器中,如集合、字符串等。Collectors类提供了多种收集器实现。

7.2 流的扁平化(flatMap

扁平化操作用于将流中的每个元素(通常是集合或数组)转换成流中的多个元素。

7.3 流的分组与分区

  • 分组:根据元素的属性或条件将元素分组。
  • 分区:将流分为两个子流,通常基于某个条件。

示例代码:高级特性的使用

import java.util.*;
import java.util.stream.*;

public class StreamAdvancedFeatures {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date", "Elderberry");

        // 使用collect自定义收集器
        Map<Boolean, List<String>> collected = fruits.stream()
                .collect(Collectors.partitioningBy(fruit -> fruit.length() > 5));
        System.out.println("Collected fruits by length: " + collected);

        // 使用flatMap进行扁平化
        List<List<String>> listOfFruits = Arrays.asList(
                Arrays.asList("Apple", "Banana"),
                Arrays.asList("Cherry", "Date", "Elderberry")
        );
        Set<String> flatCollected = listOfFruits.stream()
                .flatMap(list -> list.stream())
                .collect(Collectors.toSet());
        System.out.println("Flat collected fruits: " + flatCollected);
    }
}

这段代码演示了如何使用collect进行自定义收集,以及如何使用flatMap进行流的扁平化。

结语

在本章中,我们探讨了Stream API的一些高级特性,包括流的收集、扁平化以及分组与分区。示例代码展示了如何使用这些高级特性来处理更复杂的数据操作。

第八章:Stream API实战案例

8.1 真实场景下的Stream API应用

在这一节中,我们将探讨Stream API在解决实际问题中的应用,例如数据分析、日志处理等。

8.2 性能优化与最佳实践

  • 避免在流操作中使用复杂的Lambda表达式:这可能会导致性能下降。
  • 合理使用并行流:并行流可以在某些情况下提高性能,但也需要考虑线程管理和任务分配的开销。

示例代码:Stream API在数据分析中的应用

import java.util.*;
import java.util.stream.*;

public class StreamAPIInPractice {
    public static void main(String[] args) {
        List<Transaction> transactions = Arrays.asList(
                new Transaction("Alice", 100),
                new Transaction("Bob", 200),
                new Transaction("Alice", 50)
        );

        // 计算总交易额
        double totalAmount = transactions.stream()
                .mapToDouble(Transaction::getAmount)
                .sum();
        System.out.println("Total transaction amount: " + totalAmount);

        // 找出交易额最高的用户
        Optional<Transaction> highestTransaction = transactions.stream()
                .max(Comparator.comparingDouble(Transaction::getAmount));

        highestTransaction.ifPresent(System.out::println);

        // 按用户分组并计算每个用户的交易总额
        Map<String, Double> totalAmountPerUser = transactions.stream()
                .collect(Collectors.groupingBy(
                        Transaction::getUserName,
                        Collectors.summingDouble(Transaction::getAmount)
                ));
        System.out.println("Total amount per user: " + totalAmountPerUser);
    }
}

class Transaction {
    private final String userName;
    private final double amount;

    public Transaction(String userName, double amount) {
        this.userName = userName;
        this.amount = amount;
    }

    public String getUserName() {
        return userName;
    }

    public double getAmount() {
        return amount;
    }
}

这段代码演示了如何在一个交易记录列表上使用Stream API来执行数据分析,包括计算总交易额、找出交易额最高的交易记录,以及按用户分组计算交易总额。