Java Stream API的二度深入

2024-02-24 14:04
文章标签 java 深入 api stream 二度

本文主要是介绍Java Stream API的二度深入,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Java Stream API的二度深入

在这里插入图片描述

前言

为什么会写这样一篇文章呢?

1.面试的时候,一位前辈对我这方面有过一次提问,我随口回答,前辈很信任我,以此文致敬前辈!

2.去回顾,去扎实,对得起前辈的信任!

3.Stream API 用起来很方便,所以很重要,所以还得学!

Stream API的三个阶段

在Java中,Stream 是Java 8引入的一个新概念,用于处理集合(Collections)数据的一种抽象。Java的Stream API 提供了一种声明式的方式来操作数据集合,可以用更简洁、可读性更强的代码来进行集合的操作。

Java Stream API的操作可以分为三个阶段:

  1. 创建流(Creation of Stream): 这个阶段涉及到从不同的数据源创建流,可以是集合、数组、I/O通道等。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> stream = numbers.stream();
    
  2. 中间操作(Intermediate Operations): 这个阶段包括对流的转换操作,可以对流进行过滤、映射、排序等操作。这些操作并不会改变原始数据源,而是返回一个新的流。

    Stream<Integer> filteredStream = stream.filter(x -> x > 2);
    
  3. 终端操作(Terminal Operations): 这个阶段是对流进行最终操作,触发流的遍历,可以产生一个结果或者副作用。终端操作是流的最后一个操作,执行后流将不可再用。

    long count = filteredStream.count();
    

这三个阶段的设计使得可以通过链式调用的方式组合多个操作,从而编写更为清晰和简洁的代码。这种方式也有助于提高代码的可读性和可维护性。

当然,这里只是对于Stream API三个阶段的概述,只是告诉大家,简单分为三个阶段,至于三个阶段里面有哪些主要的方法,我们在下文进行详细叙述,这里我们点到为止!现在,大家心里面就应该有这么一个蓝图,或者是基本框架,知道我们接下来将会沿着那个几个方向展开叙述!

创建Stream

在Java中,你可以使用多种方式来创建Stream流。

  1. 从集合创建:

    使用集合类的 stream()parallelStream() 方法可以创建对应的流。例如:

    List<String> list = Arrays.asList("apple", "banana", "orange");
    Stream<String> streamFromList = list.stream();
    
  2. 从数组创建:

    使用 Arrays.stream() 方法可以从数组中创建流:

    String[] array = {"apple", "banana", "orange"};
    Stream<String> streamFromArray = Arrays.stream(array);
    
  3. 通过Stream的静态方法创建:

    Stream 类提供了静态方法 of(),可以传入一系列元素来创建流:

    Stream<String> stream = Stream.of("apple", "banana", "orange");
    
  4. 使用Stream的generateiterate方法:

    Stream 类还提供了 generateiterate 方法,用于生成无限流:

    // 生成包含随机整数的无限流
    Stream<Integer> infiniteStream = Stream.generate(() -> (int) (Math.random() * 100));// 从指定的起始值开始,按照某个规则生成无限流
    Stream<Integer> sequentialStream = Stream.iterate(1, n -> n + 1);
    
  5. 通过文件生成流:

    java.nio.file.Files 类提供了静态方法 lines(),可以用来读取文件内容并生成流:

    Path path = Paths.get("example.txt");
    Stream<String> fileLines = Files.lines(path);
    
  6. 使用正则表达式生成流:

    Pattern 类的 splitAsStream 方法可以根据正则表达式将字符串分割成流:

    String text = "apple,orange,banana";
    Stream<String> textStream = Pattern.compile(",").splitAsStream(text);
    

Stream API中间操作

Stream API 提供了许多中间操作,用于对流进行转换、筛选和处理。

  1. filter

    用于筛选元素,根据指定的条件保留符合条件的元素。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> filteredStream = numbers.stream().filter(x -> x > 2);
    
  2. map

    对流中的每个元素应用指定的函数,并将结果映射为一个新的元素。

    List<String> words = Arrays.asList("apple", "banana", "orange");
    Stream<Integer> wordLengths = words.stream().map(String::length);
    
  3. flatMap

    将流中的每个元素都转换为一个流,然后将这些流连接起来成为一个流。

    List<List<Integer>> numbers = Arrays.asList(Arrays.asList(1, 2),Arrays.asList(3, 4),Arrays.asList(5, 6)
    );Stream<Integer> flatStream = numbers.stream().flatMap(List::stream);
    
  4. distinct

    去除流中的重复元素。

    List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
    Stream<Integer> distinctNumbers = numbers.stream().distinct();
    
  5. sorted

    对流中的元素进行排序。

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

    对流中的每个元素执行操作,主要用于调试和观察流中的元素。

    List<String> words = Arrays.asList("apple", "banana", "orange");
    Stream<String> peekStream = words.stream().peek(System.out::println);
    
  7. limitskip

    limit 用于截断流,保留指定数量的元素,而 skip 则用于跳过指定数量的元素。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> limitedStream = numbers.stream().limit(3);
    Stream<Integer> skippedStream = numbers.stream().skip(2);
    
  8. takeWhiledropWhile(Java 9+):

    takeWhile 返回元素满足指定条件的最长前缀,dropWhile 返回删除满足指定条件的最长前缀后的流。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> takenWhileStream = numbers.stream().takeWhile(x -> x < 4);
    Stream<Integer> droppedWhileStream = numbers.stream().dropWhile(x -> x < 4);
    

Stream API终端操作

Stream API 的终端操作用于触发对流的最终操作,产生结果或者引起副作用。

  1. forEach

    对流中的每个元素执行指定的操作。

    List<String> words = Arrays.asList("apple", "banana", "orange");
    words.stream().forEach(System.out::println);
    
  2. toArray

    将流中的元素转换为数组。

    List<String> words = Arrays.asList("apple", "banana", "orange");
    String[] wordArray = words.stream().toArray(String[]::new);
    
  3. reduce

    对流中的元素进行归约操作,可以用于求和、求最大值、最小值等。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Optional<Integer> sum = numbers.stream().reduce(Integer::sum);
    
  4. collect

    将流中的元素收集到一个集合中,例如 List、Set 或 Map。

    List<String> words = Arrays.asList("apple", "banana", "orange");
    List<String> collectedWords = words.stream().collect(Collectors.toList());
    
  5. count

    返回流中的元素数量。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    long count = numbers.stream().count();
    
  6. anyMatchallMatchnoneMatch

    用于检查流中是否存在满足指定条件的元素。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    boolean anyGreaterThanThree = numbers.stream().anyMatch(x -> x > 3);
    boolean allGreaterThanTwo = numbers.stream().allMatch(x -> x > 2);
    boolean noneGreaterThanFive = numbers.stream().noneMatch(x -> x > 5);
    
  7. findAnyfindFirst

    返回流中的任意一个元素或者第一个元素。

    List<String> words = Arrays.asList("apple", "banana", "orange");
    Optional<String> anyWord = words.stream().findAny();
    Optional<String> firstWord = words.stream().findFirst();
    
  8. minmax

    返回流中的最小值或最大值。

    List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
    Optional<Integer> minNumber = numbers.stream().min(Integer::compare);
    Optional<Integer> maxNumber = numbers.stream().max(Integer::compare);
    

注意事项

使用Stream API时,有一些需要注意的重要事项,以确保正确、高效地利用这一功能:

  1. 只能使用一次: 一个 Stream 实例只能被消费(执行终端操作)一次。如果你尝试对已经使用过的流进行其他终端操作,会抛出 IllegalStateException 异常。如果需要再次操作,可以重新创建一个新的流。

    List<String> words = Arrays.asList("apple", "banana", "orange");
    Stream<String> wordStream = words.stream();// 正确的做法
    long count = wordStream.count();// 错误的做法,会抛出IllegalStateException
    long anotherCount = wordStream.count();
    
  2. 及早退出: 在处理大量数据时,及早退出可以提高性能。使用 anyMatch()findFirst() 等终端操作时,一旦找到符合条件的元素,就会立即返回,不再继续处理后续元素。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    boolean anyGreaterThanThree = numbers.stream().anyMatch(x -> {System.out.println("Checking: " + x);return x > 3;
    });
    
  3. 并行流的谨慎使用: Stream API 提供了并行流的支持,可以通过 parallel() 方法将顺序流转换为并行流。但并不是所有的场景都适合使用并行流,因为在某些情况下,并行流可能会导致性能下降,甚至出现并发问题。在并行流的使用上需要注意线程安全等问题。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    long count = numbers.parallelStream().filter(x -> x > 2).count();
    
  4. 避免可变状态: 在使用 map()flatMap() 等操作时,避免修改流中的元素或者使用可变状态。这有助于确保流操作的无状态性,避免副作用。

    // 避免修改流中的元素
    List<String> words = Arrays.asList("apple", "banana", "orange");
    words.stream().map(String::toUpperCase).forEach(System.out::println);// 避免使用可变状态
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    int sum = numbers.stream().reduce(0, Integer::sum);
    
  5. 了解操作的性能特性: 不同的操作对性能的影响是不同的。例如,limit() 对顺序流的性能影响较小,但对并行流的性能影响较大。在选择操作时,了解其性能特性对于优化代码是有帮助的。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> limitedStream = numbers.stream().limit(3);  // 对顺序流影响较小,但对并行流影响较大
    
  6. 使用适当的数据结构: 在创建流时,选择适当的数据结构能够影响流操作的性能。例如,ArrayList 在顺序访问时性能较好,而 LinkedList 在随机访问时性能较好。

    List<String> words = new ArrayList<>(Arrays.asList("apple", "banana", "orange"));
    Stream<String> wordStream = words.stream();
    

总体而言,了解Stream API的使用原则,结合具体的业务场景和性能需求,能够更好地利用Stream API完成任务。注意流的延迟计算特性,避免副作用,可以使代码更加清晰、可读,并提高代码的可维护性。

这篇关于Java Stream API的二度深入的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/742326

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于