Java 8 Stream API实战指南

Java 8 Stream API实战指南

【免费下载链接】java8-tutorial Modern Java - A Guide to Java 8 【免费下载链接】java8-tutorial 项目地址: https://gitcode.com/gh_mirrors/ja/java8-tutorial

Java 8 Stream API是函数式编程风格的核心组件,提供高效、声明式的方式来处理数据集合。本文全面介绍Stream API的核心概念、创建方式、中间操作、终端操作以及并行流性能优化技巧,帮助开发者掌握这一强大的数据处理工具。

Stream API核心概念与创建方式

Java 8 Stream API是函数式编程风格的核心组件,它提供了一种高效、声明式的方式来处理数据集合。Stream不是数据结构,而是对数据源进行函数式操作的流水线,支持并行处理和大数据操作。

Stream的核心特性

Stream API具有以下几个关键特性:

  • 非存储性:Stream本身不存储数据,只是对数据源进行操作
  • 函数式操作:支持lambda表达式和方法引用
  • 惰性求值:中间操作都是惰性的,只有在终止操作时才会执行
  • 可并行化:支持并行处理提高性能
  • 不可变性:操作不会修改原始数据源

Stream操作类型分类

Stream操作可以分为两大类:

操作类型描述示例方法
中间操作返回Stream,可链式调用filter(), map(), sorted(), distinct()
终止操作返回具体结果或产生副作用forEach(), collect(), reduce(), count()

创建Stream的多种方式

1. 从集合创建Stream

最常用的方式是从现有的Collection接口实现类创建Stream:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

// 创建顺序流
Stream<String> sequentialStream = names.stream();

// 创建并行流  
Stream<String> parallelStream = names.parallelStream();
2. 使用Stream.of()静态方法

直接通过值创建Stream:

Stream<String> stringStream = Stream.of("Java", "Python", "JavaScript");
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
Stream<Double> doubleStream = Stream.of(1.1, 2.2, 3.3);
3. 从数组创建Stream

使用Arrays.stream()方法从数组创建Stream:

String[] languages = {"Java", "C++", "Python", "Go"};
Stream<String> arrayStream = Arrays.stream(languages);

int[] numbers = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(numbers);
4. 使用Stream.generate()生成无限流

生成由Supplier提供的无限序列:

// 生成随机数流
Stream<Double> randomStream = Stream.generate(Math::random)
    .limit(10);

// 生成常量流
Stream<String> constantStream = Stream.generate(() -> "Hello")
    .limit(5);
5. 使用Stream.iterate()生成迭代流

基于初始值和UnaryOperator生成序列:

// 生成斐波那契数列
Stream.iterate(new int[]{0, 1}, n -> new int[]{n[1], n[0] + n[1]})
    .limit(10)
    .map(n -> n[0])
    .forEach(System.out::println);

// 生成等差数列
Stream.iterate(0, n -> n + 2)
    .limit(10)
    .forEach(System.out::println);
6. 从文件创建Stream

使用Files.lines()从文件创建行流:

try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
    lines.filter(line -> line.contains("error"))
         .forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
7. 基本类型特化流

Java 8提供了专门的基本类型流以提高性能:

IntStream.range(1, 10)         // 1到9的整数流
    .forEach(System.out::println);

IntStream.rangeClosed(1, 10)   // 1到10的整数流
    .forEach(System.out::println);

DoubleStream.of(1.1, 2.2, 3.3) // 双精度浮点数流
    .average()
    .ifPresent(System.out::println);

Stream创建方式对比表

创建方式适用场景特点示例
Collection.stream()现有集合处理最常用,支持并行list.stream()
Stream.of()已知元素序列简单直接Stream.of(1,2,3)
Arrays.stream()数组处理类型安全Arrays.stream(arr)
Stream.generate()无限序列需要limit()限制Stream.generate(Math::random)
Stream.iterate()数学序列基于迭代函数Stream.iterate(0, n->n+1)
Files.lines()文件处理自动资源管理Files.lines(path)

Stream操作流程图

mermaid

实际应用示例

// 综合示例:从多种来源创建并处理Stream
public class StreamCreationExamples {
    
    public static void main(String[] args) {
        // 示例1:从集合创建
        List<String> programmingLanguages = Arrays.asList(
            "Java", "Python", "JavaScript", "C++", "Go", "Rust"
        );
        
        long count = programmingLanguages.stream()
            .filter(lang -> lang.length() > 3)
            .map(String::toUpperCase)
            .sorted()
            .count();
        
        System.out.println("过滤后的语言数量: " + count);
        
        // 示例2:使用Stream.of创建
        Stream.of("Apple", "Banana", "Cherry")
            .map(fruit -> fruit + " Juice")
            .forEach(System.out::println);
        
        // 示例3:生成无限流(有限制)
        Stream.generate(() -> (int) (Math.random() * 100))
            .limit(5)
            .forEach(System.out::println);
            
        // 示例4:迭代生成序列
        Stream.iterate(1, n -> n * 2)
            .limit(10)
            .forEach(System.out::println);
    }
}

性能考虑与最佳实践

  1. 选择合适的创建方式:根据数据源类型选择最合适的创建方法
  2. 优先使用基本类型流:IntStream、DoubleStream等性能更好
  3. 合理使用并行流:数据量大时考虑parallelStream()
  4. 注意无限流:使用limit()限制无限流的大小
  5. 资源管理:使用try-with-resources管理文件流等资源

Stream API的创建方式灵活多样,为不同场景提供了合适的解决方案。掌握这些创建方法是为后续的流操作打下坚实基础的关键步骤。

中间操作:filter、map、sorted等

Java 8 Stream API的核心魅力在于其丰富的中间操作,这些操作允许我们对数据流进行各种转换和处理,而不会立即执行计算。中间操作是惰性的,只有在遇到终端操作时才会真正执行。让我们深入探讨三个最常用的中间操作:filter、map和sorted。

filter操作:数据筛选的艺术

filter操作用于从流中筛选出满足特定条件的元素。它接收一个Predicate函数式接口作为参数,返回一个包含所有匹配元素的新流。

List<String> stringCollection = Arrays.asList("ddd2", "aaa2", "bbb1", "aaa1", 
                                             "bbb3", "ccc", "bbb2", "ddd1");

// 筛选所有以"a"开头的字符串
stringCollection.stream()
    .filter(s -> s.startsWith("a"))
    .forEach(System.out::println);
// 输出: "aaa2", "aaa1"

filter操作的应用场景非常广泛:

应用场景示例代码说明
数值筛选.filter(n -> n > 0)筛选正数
字符串匹配.filter(s -> s.contains("java"))包含特定子串
对象属性筛选.filter(p -> p.getAge() > 18)基于对象属性筛选
空值过滤.filter(Objects::nonNull)过滤null值

map操作:数据转换的利器

map操作用于将流中的每个元素映射为另一个元素,通常用于数据转换或提取特定属性。它接收一个Function函数式接口作为参数。

// 将字符串转换为大写
stringCollection.stream()
    .map(String::toUpperCase)
    .forEach(System.out::println);
// 输出: "DDD2", "AAA2", "BBB1", "AAA1", "BBB3", "CCC", "BBB2", "DDD1"

// 提取对象属性
List<Person> persons = Arrays.asList(
    new Person("Alice", 25), 
    new Person("Bob", 30),
    new Person("Charlie", 35)
);

List<String> names = persons.stream()
    .map(Person::getName)
    .collect(Collectors.toList());
// names: ["Alice", "Bob", "Charlie"]

map操作的类型变体:

操作类型方法签名用途
map<R> Stream<R> map(Function<T, R>)通用映射
mapToIntIntStream mapToInt(ToIntFunction<T>)映射到int流
mapToDoubleDoubleStream mapToDouble(ToDoubleFunction<T>)映射到double流
mapToLongLongStream mapToLong(ToLongFunction<T>)映射到long流

sorted操作:数据排序的专家

sorted操作用于对流中的元素进行排序。它有两个重载版本:无参版本使用自然顺序排序,有参版本接受Comparator来自定义排序规则。

// 自然顺序排序
stringCollection.stream()
    .sorted()
    .forEach(System.out::println);
// 输出: "aaa1", "aaa2", "bbb1", "bbb2", "bbb3", "ccc", "ddd1", "ddd2"

// 自定义排序(降序)
stringCollection.stream()
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);
// 输出: "ddd2", "ddd1", "ccc", "bbb3", "bbb2", "bbb1", "aaa2", "aaa1"

// 组合使用:先筛选再排序
stringCollection.stream()
    .filter(s -> s.startsWith("a"))
    .sorted()
    .forEach(System.out::println);
// 输出: "aaa1", "aaa2"

操作链的威力:组合使用中间操作

真正的强大之处在于将这些中间操作组合使用,形成操作链:

stringCollection.stream()
    .filter(s -> s.startsWith("b"))      // 筛选以"b"开头的
    .map(String::toUpperCase)            // 转换为大写
    .sorted()                            // 排序
    .forEach(System.out::println);       // 输出结果
// 输出: "BBB1", "BBB2", "BBB3"

让我们通过流程图来理解这个操作链的执行过程:

mermaid

性能考虑和最佳实践

  1. 操作顺序优化:将filter操作放在map操作之前可以减少不必要的映射计算
  2. 避免重复操作:对已排序的流再次排序是浪费资源的
  3. 使用特定类型的流:对于数值操作,使用IntStream、DoubleStream等可以获得更好的性能
// 优化示例:先筛选再映射
IntStream.range(0, 100)
    .filter(i -> i % 2 == 0)      // 先筛选偶数
    .map(i -> i * 2)              // 再乘以2
    .forEach(System.out::println);

// 对比:不优化的写法(性能较差)
IntStream.range(0, 100)
    .map(i -> i * 2)              // 所有元素都乘以2
    .filter(i -> i % 4 == 0)      // 再筛选4的倍数
    .forEach(System.out::println);

实际应用案例

案例1:数据处理管道

List<Transaction> transactions = getTransactions();

// 构建复杂的数据处理管道
List<String> result = transactions.stream()
    .filter(t -> t.getType() == Transaction.Type.GROCERY)  // 筛选杂货交易
    .filter(t -> t.getValue() > 100)                       // 筛选金额大于100
    .sorted(Comparator.comparing(Transaction::getValue))   // 按金额排序
    .map(Transaction::getMerchant)                         // 提取商户名称
    .distinct()                                            // 去重
    .collect(Collectors.toList());                         // 收集结果

案例2:多层数据转换

List<Employee> employees = getEmployees();

// 多层转换和筛选
Map<String, List<String>> departmentSkills = employees.stream()
    .filter(e -> e.getAge() > 25)                          // 筛选年龄大于25
    .filter(e -> e.getSalary() > 50000)                    // 筛选薪资大于50000
    .sorted(Comparator.comparing(Employee::getDepartment)) // 按部门排序
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.mapping(Employee::getPrimarySkill, Collectors.toList())
    ));

通过熟练掌握filter、map、sorted等中间操作,你可以构建出强大而优雅的数据处理管道,让代码既简洁又高效。这些操作的组合使用是Java 8函数式编程风格的核心体现。

终端操作:forEach、collect、reduce等

Java 8 Stream API的终端操作是整个流处理流程的终点,它们会触发流的执行并产生最终结果。终端操作可以分为三大类:迭代操作、聚合操作和收集操作,每种操作都有其特定的使用场景和优势。

forEach:简单迭代操作

forEach 是最常用的终端操作之一,它用于对流中的每个元素执行指定的操作。与传统的for循环相比,forEach 提供了更加函数式的编程方式。

List<String> stringCollection = Arrays.asList("ddd2", "aaa2", "bbb1", "aaa1", "bbb3", "ccc", "bbb2", "ddd1");

// 使用forEach输出所有元素
stringCollection.stream()
    .filter(s -> s.startsWith("a"))
    .forEach(System.out::println);
// 输出: "aaa2", "aaa1"

// 使用forEach进行复杂操作
IntStream.range(0, 10)
    .forEach(i -> {
        if (i % 2 == 1) {
            System.out.println(i);
        }
    });
// 输出: 1, 3, 5, 7, 9

forEach 操作的特点:

  • 不保证元素的处理顺序(在并行流中)
  • 没有返回值,主要用于产生副作用
  • 适合执行简单的输出、日志记录等操作

collect:强大的收集器

collect 是Stream API中最强大的终端操作,它使用Collector来将流中的元素累积到不同的数据结构中。Java 8提供了丰富的内置收集器。

List<Person> persons = Arrays.asList(
    new Person("Max", 18),
    new Person("Peter", 23),
    new Person("Pamela", 23),
    new Person("David", 12)
);

// 转换为List
List<Person> filtered = persons.stream()
    .filter(p -> p.name.startsWith("P"))
    .collect(Collectors.toList());
// 结果: [Peter, Pamela]

// 分组操作
Map<Integer, List<Person>> personsByAge = persons.stream()
    .collect(Collectors.groupingBy(p -> p.age));
// 结果: {18=[Max], 23=[Peter, Pamela], 12=[David]}

// 统计信息
IntSummaryStatistics ageSummary = persons.stream()
    .collect(Collectors.summarizingInt(p -> p.age));
// 结果: IntSummaryStatistics{count=4, sum=76, min=12, average=19.0, max=23}

// 字符串连接
String names = persons.stream()
    .filter(p -> p.age >= 18)
    .map(p -> p.name)
    .collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));
// 结果: "In Germany Max and Peter and Pamela are of legal age."
自定义收集器

除了内置收集器,我们还可以创建自定义收集器:

Collector<Person, StringJoiner, String> personNameCollector = Collector.of(
    () -> new StringJoiner(" | "),          // supplier: 创建初始容器
    (j, p) -> j.add(p.name.toUpperCase()),  // accumulator: 累积元素
    (j1, j2) -> j1.merge(j2),               // combiner: 合并容器(并行流使用)
   

【免费下载链接】java8-tutorial Modern Java - A Guide to Java 8 【免费下载链接】java8-tutorial 项目地址: https://gitcode.com/gh_mirrors/ja/java8-tutorial

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

抵扣说明:

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

余额充值