Java JDK1.8 核心特性详解------Stream(流)的基本介绍

本文深入探讨Java Stream API,对比传统集合操作,展示Stream如何简化数据处理,通过实例讲解流的声明式编程、复合性和并行处理优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在前面的章节(Java JDK1.8 核心特性详解------Lambda表达式与方法引用),我们讲述了行为参数化以及Lambda表达式,在下面几篇文章里,我们会学习Stream的使用。


流是什么?

Stream流是 Java API的心成员,它允许你使用声明的方式处理数据集合。我们可以把流当作一种更加高级的迭代器。通过流我们可以更加方便的顺序或者并行的处理集合。下面将用例子让你先感受一下Stream的便利。假如我们要在一个List集合中筛选出年龄小于40岁的人,并且按照年龄进行排序,最后获取满足要求人的姓名。我们分别用传统方式和Stream的方式获取最终结果:

        //初始化数据
        List<People> peopleList = new ArrayList<>();
        peopleList.add(new People("张三", 15));
        peopleList.add(new People("李四", 41));
        peopleList.add(new People("赵六", 35));
        peopleList.add(new People("王五", 48));
        peopleList.add(new People("啊大", 14));
        peopleList.add(new People("啊二", 16));
----------------------------------------------------------------------------------------
        //普通方式
        List<People> peopleArrayList = new ArrayList<>();
        //筛选出年龄小于40岁的人
        for (People people : peopleList) {
            if (people.getAge() < 40) {
                peopleArrayList.add(people);
            }
        }
        //将人按照年龄从小到大排序
        peopleArrayList.sort(Comparator.comparing(People::getAge));
        //提取人的名字
        List<String> nameList = new ArrayList<>();
        for (People people:peopleArrayList) {
            nameList.add(people.getName());
        }

        List<People> peopleArrayList = new ArrayList<>();
        //筛选出年龄小于40岁的人
        for (People people : peopleList) {
            if (people.getAge() < 40) {
                peopleArrayList.add(people);
            }
        }
        //将人按照年龄从小到大排序
        peopleArrayList.sort(Comparator.comparing(People::getAge));
        //提取人的名字
        List<String> nameList = new ArrayList<>();
        for (People people:peopleArrayList) {
            nameList.add(people.getName());
        }

        System.out.println(nameList);
----------------------------------------------------------------------------------------
        //Stream方法
        List<String> nameList2 = peopleList.stream()
        //过滤出年龄小于40的人
        .filter(people1 -> people1.getAge() < 40)
        //按照年龄排序
        .sorted(Comparator.comparing(People::getAge))
        //获取名称
        .map(People::getName)
        //返回结果
        .collect(Collectors.toList());

        System.out.println(nameList2);

可以看到,在普通方法中,我们多创建了一个peopleArrayList 垃圾变量,同时,在代码量上也多了很多。除此之外,如果我们想并行处理集合,只需要把people.stream改成people.parallelStream,流会自动帮你使用并行的方式处理数据,这在多核CPU上会提升处理效率。目前来看,流有几个显而易见的好处:

  • 申明性:代码是以声明式方式写的:就像SQL语句一样,只是说明想完成什么,而不是说明如何实现。用这种方式加上行为参数化可以让我们很方便的应对需求

  • 可复合:我们可以把几个基础操作链接起来,来表达复杂数据的处理流水线,同时保证代码清晰可读

  • 可并行:我们可以简单的将集合并行处理,使性能更好。

流的简介

Stream相关类和接口在java.util.stream包中,我们可以通过很多方式获取到流,比如利用数值范围或者I/O资源生成流。流简短的定义就是“从支持数据处理操作的源生成的元素序列”。

  • 元素序列:和集合一样,流提供了一个接口,可以访问特定元素的一组有序值。因为集合时数据结构,所以它的主要目的是以特定的时间/空间复杂度储存和访问数据。但流的目的是表达计算。因此,集合讲的是数据,流讲的是计算
  • 源:流会从源中获取要处理的流,有序集合生成流时会保持原来的顺序。
  • 数据处理操作:对数据进行处理,包括过滤,排序,遍历等。流操作还可以顺序执行和并行执行。
  • 流水线:很多流操作会返回处理后的流,这样我们就可以将多个操作链接起来。
  • 内部迭代:流的迭代是在背后进行的,跟迭代器显式迭代不太一样。

流和集合的区别

什么时候进行读取和计算:

集合是一个内存中的数据结构,包含数据结构中目前所有的值,我们要先把所有的数据加载到集合中以后才可以对集合进行操作。就像我们我们用DVD看电影,包含电影的所有东西。要看必须要有完整的电影。流则是在概念上的固定的数据结构,其元素则是按需计算。例如我们在网上看电影,只需要加载目前进度的后面几分钟的电影就行。这样做有很大的好处,就是只有在我们需要的时候才会加载值。后面会有一个例子证明这点。

流只能遍历一次:

流和迭代器一样只能遍历一次,或者说只能消费一次。如果我们对一个流消费两次,那么代码会抛出java.lang.IllegalStateException:流已被操作或关闭。除非我们从数据源那再获取一个流。于此相反,我们可以对集合进行任意次的操作。

内部迭代和外部迭代:

我们使用集合或者迭代器的时候,需要我们自己去声明如何进行迭代,例如使用for循环或者是hasNext显性的去获取每一个元素,然后再在方法体中说明如何操作这些元素。这个就是外部迭代。对于流来说,你只需要申明对流进行什么操作。流会自动在内部对元素进行迭代。这就是内部迭代。

 流的操作

java.util.stream.Stream中的Stream接口定义了很多操作。它们可以分成两大类:中间操作和终端操作。回顾一下上面的例子,讲述两类的区别:

        List<String> nameList2 = peopleList
        //获取流
        .stream()
        //中间操作
        .filter(people1 -> people1.getAge() < 40)
        //中间操作
        .sorted(Comparator.comparing(People::getAge))
        //中间操作
        .map(People::getName)
        //终端操作
        .collect(Collectors.toList());

像filter、sorted、map等操作可以返回流,与其他操作连成一条流水线的流操作叫做中间操作。像collect这种会关闭流返回其他对象的操作叫做终端操作 

中间操作

中间操作会返回一个新的流,例如从peopleList.stream()产生的流,在经过filter()以后,会产生一个新的流,给sorted()使用,在sorted()使用以后会产生一个流给map。这样不断的传递下去,就相当于工厂的流水线。还有很重要的一点,除非流水线触发一个终端操作,不然中间操作不会执行任何处理。因为中间操作能够合并起来,在终端操作时一次性全部处理。

        List<String> nameList2 = peopleList.stream()
                //过滤出年龄小于40的人
                .filter(people1 ->{System.out.println("filter:"+people1.getAge());
                    return people1.getAge()<40;} )
                //获取名称
                .map(people -> {
                    System.out.println("map"+people.getName());
                    return people.getName();
                })
                //直取前三个
                .limit(2)
                //返回结果
                .collect(Collectors.toList());

这个代码将会打印:

filter:15
map张三
filter:41
filter:35
map赵六

 可以看出,流操作先将一个元素经过全部的中间操作以后,才会去执行另一个操作。这里利用了流的延迟性质。第一,尽管有很多人,但是我们只查询了前三个人就获得了我们要的结果。其次,我们将过滤和获取信息两个独立的操作合并到一次遍历中(可以更加清楚的看出我们的遍历过程,这个技术叫循环合并)。

终端操作

终端操作会从流水线中生成结果。返回结果可以是List,Integer,甚至是void。

使用流

一般来说,流的使用包括三个方面:

  • 从数据源中创建一个流

  • 对流进行中间操作

  • 对流进行终端操作

下面两张图是常用的流操作:

流当然还有更多的操作,以及许多模式,比如切片、查找、匹配等,会在后面慢慢说。


更多与JDK1.8相关的文章请看:Java JDK1.8 核心特性详解----(总目录篇)

<think>好的,用户想了解JDK 1.8中lang包下的Void类的源码解析。首先我需要回忆一下Void类的基本信息。Void类是java.lang包中的一个final类,主要用于反射操作,比如获取表示void返回类型的Class对象。它和原始类型void相关联,但本身是一个占位符类,不能被实例化。 接下来,我需要查阅JDK 1.8的源码,确认Void类的具体实现。根据记忆,Void类的代码应该非常简短,因为它主要是一个包装类,没有实际的方法。源码中应该包含一个私有构造函数防止实例化,一个公共静态的Class<Void> TYPE字段,可能通过反射或者JVM内部机制初始化为void.class。 然后,考虑用户提供的引用资料。引用[1]提到了Object类中的native方法,引用[2]涉及类加载器,引用[3]是File类的方法,引用[4]关于getClass()方法,引用[5]关于Stream。虽然这些引用没有直接提到Void类,但需要确保解析时提到的类加载机制、反射使用等部分与现有引用不冲突,比如引导类加载器在Java层显示为null,这可能和Void类的TYPE字段初始化有关,需要确认是否正确。 用户可能想知道Void类的设计目的、结构、主要用途,比如在反射中获取void类型,或者作为泛型参数占位符。需要解释Void类不可实例化,TYPE字段的作用,以及实际应用场景,比如搭配Callable<Void>使用,虽然返回null。 需要注意源码中的细节,比如构造函数是否为私有,TYPE字段如何初始化。同时要确保LaTeX格式正确,行内公式用$...$,独立公式用$$...$$,但Void类解析可能涉及较少数学公式,但如果有类型转换或反射相关的表达式,需要正确应用格式。 最后生成相关问题时要围绕Void类的使用场景、设计原理、与void关键字的区别,以及相关反射机制。确保问题有助于用户进一步理解相关知识,并参考已有的引用资料中的类加载、native方法等内容,保持一致性。</think>### JDK 1.8 `java.lang.Void` 源码解析 #### 1. 类定义与设计目的 `Void` 类是 `java.lang` 包中一个特殊的 **`final`** 类,设计目的是为 `void` 关键字提供类型占位符。它主要用于反射操作,例如通过 `Method.getReturnType()` 获取 `void` 方法的返回类型时,实际返回的是 `Void.TYPE` 字段[^4]。 类定义如下: ```java public final class Void { private Void() {} public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void"); } ``` #### 2. 源码分析 1. **私有构造函数** `private Void() {}` 确保无法实例化该类的对象,符合其作为占位符的特性[^1]。 2. **`TYPE` 字段** `public static final Class<Void> TYPE` 是核心字段,通过 `Class.getPrimitiveClass("void")` 获取 `void` 类型的 `Class` 对象。此方法由 JVM 内部实现(`native` 方法),直接关联原始类型 `void`[^2]。 #### 3. 关键特性 - **与 `void` 的关系** `Void.TYPE` 等价于 `void.class`,例如: ```java System.out.println(Void.TYPE == void.class); // 输出 true ``` - **泛型占位符** 在需要明确表示“无返回值”的泛型场景中使用,例如: ```java Callable<Void> task = () -> { System.out.println("执行任务"); return null; // 必须返回 null }; ``` #### 4. 应用场景 - **反射操作** 通过 `TYPE` 字段判断方法是否返回 `void` 类型: ```java Method method = MyClass.class.getMethod("myMethod"); if (method.getReturnType() == Void.TYPE) { System.out.println("该方法返回 void"); } ``` - **避免 `Null` 的占位符** 在 `Future<Void>` 或 `RunnableFuture<Void>` 中表示异步任务无返回值[^5]。 #### 5. 与 `void` 的区别 | 特性 | `void` 关键字 | `Void` 类 | |--------------------|-----------------------|-------------------------| | 类型 | 原始类型 | 包装类 | | 实例化 | 不可 | 不可(构造函数私有化) | | 用途 | 方法返回值声明 | 反射、泛型占位符 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值