# 第 18 章_JDK8-17 新特性(上)

讲师:尚硅谷 - 宋红康(江湖人称:康师傅)

官网:http://www.atguigu.com


# 本章专题与脉络

JDK8 的新特性:

  • Java 版本迭代概述

  • Lambda 表达式

    • 函数式接口
    • 方法引用、构造器引用、数组引用
  • Stream API

第3阶段:Java高级应用-第18章


# 1. Java 版本迭代概述

# 1.1 发布特点(小步快跑,快速迭代)

发行版本发行时间备注
Java 1.01996.01.23Sun 公司发布了 Java 的第一个开发工具包
Java 5.02004.09.30①版本号从 1.4 直接更新至 5.0;②平台更名为 JavaSE、JavaEE、JavaME
Java 8.02014.03.18此版本是继 Java 5.0 以来变化最大的版本。是长期支持版本( LTS
Java 9.02017.09.22此版本开始,每半年更新一次
Java 10.02018.03.21
Java 11.02018.09.25JDK 安装包取消独立 JRE 安装包,是长期支持版本( LTS
Java 12.02019.03.19
......
Java17.02021.09发布 Java 17.0,版本号也称为 21.9,是长期支持版本( LTS
......
Java19.02022.09发布 Java19.0,版本号也称为 22.9。

从 Java 9 这个版本开始,Java 的计划发布周期是 6个月

这意味着 Java 的更新从传统的以 特性驱动 的发布周期,转变为以 时间驱动 的发布模式,并且承诺不会跳票。通过这样的方式,开发团队可以把一些关键特性尽早合并到 JDK 之中,以快速得到开发者反馈,在一定程度上避免出现像 Java 9 两次被迫延迟发布的窘况。

针对企业客户的需求,Oracle 将以 三年 为周期发布 长期支持版本(long term support)

Oracle 的官方观点认为:与 Java 7->8->9 相比,Java 9->10->11 的升级和 8->8u20->8u40 更相似。

新模式下的 Java 版本发布都会包含许多变更,包括 语言变更JVM 变更 ,这两者都会对 IDE、字节码库和框架产生重大影响。此外,不仅会新增其他 API,还会有 API被删除 (这在 Java 8 之前没有发生过)。

目前看这种发布策略是非常成功的,解开了 Java/JVM 演进的许多枷锁,至关重要的是,OpenJDK 的权力中心,正在转移到开发社区和开发者手中。在新的模式中,既可以利用 LTS 满足企业长期可靠支持的需求,也可以满足各种开发者对于新特性迭代的诉求。因为用 2-3 年的最小间隔粒度来试验一个特性,基本是不现实的。

# 1.2 名词解释

# 名词解释:Oracle JDK 和 Open JDK

这两个 JDK 最大不同就是许可证不一样。但是对于个人用户来讲,没区别。

Oracle JDKOpen JDK
来源Oracle 团队维护Oracle 和 Open Java 社区
授权协议Java 17 及更高版本 Oracle Java SE 许可证
Java16 及更低版本甲骨文免费条款和条件 (NFTC) 许可协议
GPL v2 许可证
关系由 Open JDK 构建,增加了少许内容
是否收费2021 年 9 月起 Java17 及更高版本所有用户免费。 16 及更低版本,个人用户、开发用户免费。2017 年 9 月起,所有版本免费
对语法的支持一致一致

# 名词解释:JEP

JEP(JDK Enhancement Proposals):jdk 改进提案 ,每当需要有新的设想时候,JEP 可以提出非正式的规范 (specification),被正式认可的 JEP 正式写进 JDK 的发展路线图并分配版本号。

# 名词解释:LTS

LTS(Long-term Support):长期支持 。Oracle 官网提供了对 Oracle JDK 个别版本的长期支持,即使发发行了新版本,比如目前最新的 JDK19,在结束日期前,LTS 版本都会被长期支持。(出了 bug,会被修复,非 LTS 则不会再有补丁发布)所以,一定要选一个 LTS 版本,不然出了漏洞没人修复了。

版本开始日期结束日期延期结束日期
7(LTS)2011 年 7 月2019 年 7 月2022 年 7 月
8(LTS)2014 年 3 月2022 年 3 月2030 年 12 月
11(LTS)2018 年 9 月2023 年 9 月2026 年 9 月
17(LTS)2021 年 9 月2026 年 9 月2029 年 9 月
21(LTS)2023 年 9 月2028 年 9 月2031 年 9 月

如果要选择 Oracle JDK,目前可选的 LTS 版本为 8、11、17 三个。

# 1.3 各版本支持时间路线图

4428b288361296048

# 1.4 各版本介绍

jdkxintexing

# jdk 9

Java 9 提供了 超过150项 新功能特性,包括备受期待的模块化系统、可交互的 REPL 工具:jshell,JDK 编译工具,Java 公共 API 和私有代码,以及安全增强、扩展提升、性能管理改善等。

特性太多,查看链接:

https://openjdk.java.net/projects/jdk9/

# jdk 10

https://openjdk.java.net/projects/jdk/10/

286: Local-Variable Type Inference 局部变量类型推断
296: Consolidate the JDK Forest into a Single Repository JDK 库的合并
304: Garbage-Collector Interface 统一的垃圾回收接口
307: Parallel Full GC for G1 为 G1 提供并行的 Full GC
310: Application Class-Data Sharing 应用程序类数据(AppCDS)共享
312: Thread-Local Handshakes ThreadLocal 握手交互
313: Remove the Native-Header Generation Tool (javah) 移除 JDK 中附带的 javah 工具
314: Additional Unicode Language-Tag Extensions 使用附加的 Unicode 语言标记扩展
316: Heap Allocation on Alternative Memory Devices 能将堆内存占用分配给用户指定的备用内存设备
317: Experimental Java-Based JIT Compiler 使用 Graal 基于 Java 的编译器

319: Root Certificates 根证书
322: Time-Based Release Versioning 基于时间定于的发布版本

# jdk 11

https://openjdk.java.net/projects/jdk/11/

181: Nest-Based Access Control 基于嵌套的访问控制
309: Dynamic Class-File Constants 动态类文件常量
315: Improve Aarch64 Intrinsics 改进 Aarch64 Intrinsics
318: Epsilon: A No-Op Garbage Collector Epsilon — 一个 No-Op(无操作)的垃圾收集器
320: Remove the Java EE and CORBA Modules 删除 Java EE 和 CORBA 模块
321: HTTP Client (Standard) HTTPClient API
323: Local-Variable Syntax for Lambda Parameters 用于 Lambda 参数的局部变量语法
324: Key Agreement with Curve25519 and Curve448 Curve25519 和 Curve448 算法的密钥协议
327: Unicode 10
328: Flight Recorder 飞行记录仪
329: ChaCha20 and Poly1305 Cryptographic Algorithms ChaCha20 和 Poly1305 加密算法
330: Launch Single-File Source-Code Programs 启动单一文件的源代码程序
331: Low-Overhead Heap Profiling 低开销的 Heap Profiling
332: Transport Layer Security (TLS) 1.3 支持 TLS 1.3
333: ZGC: A Scalable Low-Latency Garbage Collector
(Experimental)
可伸缩低延迟垃圾收集器
335: Deprecate the Nashorn JavaScript Engine 弃用 Nashorn JavaScript 引擎
336: Deprecate the Pack200 Tools and API 弃用 Pack200 工具和 API

# jdk 12

https://openjdk.java.net/projects/jdk/12/

189:Shenandoah: A Low-Pause-Time Garbage Collector (Experimental) 低暂停时间的 GC
230: Microbenchmark Suite 微基准测试套件
325: Switch Expressions (Preview) switch 表达式
334: JVM Constants API JVM 常量 API
340: One AArch64 Port, Not Two 只保留一个 AArch64 实现
341: Default CDS Archives 默认类数据共享归档文件
344: Abortable Mixed Collections for G1 可中止的 G1 Mixed GC
346: Promptly Return Unused Committed Memory from G1 G1 及时返回未使用的已分配内存

# jdk 13

https://openjdk.java.net/projects/jdk/13/

350: Dynamic CDS Archives 动态 CDS 档案
351: ZGC: Uncommit Unused Memory ZGC: 取消使用未使用的内存
353: Reimplement the Legacy Socket API 重新实现旧版套接字 API
354: Switch Expressions (Preview) switch 表达式(预览)
355: Text Blocks (Preview) 文本块(预览)

# jdk 14

https://openjdk.java.net/projects/jdk/14/

305: Pattern Matching for instanceof (Preview) instanceof 的模式匹配
343: Packaging Tool (Incubator) 打包工具
345: NUMA-Aware Memory Allocation for G1 G1 的 NUMA-Aware 内存分配
349: JFR Event Streaming JFR 事件流
352: Non-Volatile Mapped Byte Buffers 非易失性映射字节缓冲区
358: Helpful NullPointerExceptions 实用的 NullPointerExceptions
359: Records (Preview)
361: Switch Expressions (Standard) Switch 表达式
362: Deprecate the Solaris and SPARC Ports 弃用 Solaris 和 SPARC 端口
363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector 删除并发标记扫描(CMS)垃圾回收器
364: ZGC on macOS
365: ZGC on Windows
366: Deprecate the ParallelScavenge + SerialOld GC Combination 弃用 ParallelScavenge + SerialOld GC 组合
367: Remove the Pack200 Tools and API 删除 Pack200 工具和 API
368: Text Blocks (Second Preview) 文本块
370: Foreign-Memory Access API (Incubator) 外部存储器访问 API

# jdk 15

https://openjdk.java.net/projects/jdk/15/

339: Edwards-Curve Digital Signature Algorithm (EdDSA) EdDSA 数字签名算法
360: Sealed Classes (Preview) 密封类(预览)
371: Hidden Classes 隐藏类
372: Remove the Nashorn JavaScript Engine 移除 Nashorn JavaScript 引擎
373: Reimplement the Legacy DatagramSocket API 重新实现 Legacy DatagramSocket API
374: Disable and Deprecate Biased Locking 禁用偏向锁定
375: Pattern Matching for instanceof (Second Preview) instanceof 模式匹配(第二次预览)
377: ZGC: A Scalable Low-Latency Garbage Collector ZGC:一个可扩展的低延迟垃圾收集器
378: Text Blocks 文本块
379: Shenandoah: A Low-Pause-Time Garbage Collector Shenandoah: 低暂停时间垃圾收集器
381: Remove the Solaris and SPARC Ports 移除 Solaris 和 SPARC 端口
383: Foreign-Memory Access API (Second Incubator) 外部存储器访问 API(第二次孵化版)
384: Records (Second Preview) Records(第二次预览)
385: Deprecate RMI Activation for Removal 废弃 RMI 激活机制

# jdk 16

https://openjdk.java.net/projects/jdk/16/

338: Vector API (Incubator) Vector API(孵化器)
347: Enable C++14 Language Features JDK C 的源码中允许使用 C14 的语言特性
357: Migrate from Mercurial to Git OpenJDK 源码的版本控制从 Mercurial (hg) 迁移到 git
369: Migrate to GitHub OpenJDK 源码的版本控制迁移到 github 上
376: ZGC: Concurrent Thread-Stack Processing ZGC:并发线程处理
380: Unix-Domain Socket Channels Unix 域套接字通道
386: Alpine Linux Port 将 glibc 的 jdk 移植到使用 musl 的 alpine linux 上
387: Elastic Metaspace 弹性元空间
388: Windows/AArch64 Port 移植 JDK 到 Windows/AArch64
389: Foreign Linker API (Incubator) 提供 jdk.incubator.foreign 来简化 native code 的调用
390: Warnings for Value-Based Classes 提供基于值的类的警告
392: Packaging Tool jpackage 打包工具转正
393: Foreign-Memory Access API (Third Incubator)
394: Pattern Matching for instanceof Instanceof 的模式匹配转正
395: Records Records 转正
396: Strongly Encapsulate JDK Internals by Default 默认情况下,封装了 JDK 内部构件
397: Sealed Classes (Second Preview) 密封类

# jdk 17

https://openjdk.java.net/projects/jdk/17/

306: Restore Always-Strict Floating-Point Semantics 恢复始终严格的浮点语义

356: Enhanced Pseudo-Random Number Generators 增强型伪随机数生成器

382: New macOS Rendering Pipeline 新的 macOS 渲染管道

391: macOS/AArch64 Port macOS/AArch64 端口

398: Deprecate the Applet API for Removal 弃用 Applet API 后续将进行删除

403: Strongly Encapsulate JDK Internals 强封装 JDK 的内部 API

406: Pattern Matching for switch (Preview) switch 模式匹配(预览)

407: Remove RMI Activation 删除 RMI 激活机制

409: Sealed Classes 密封类转正

410: Remove the Experimental AOT and JIT Compiler 删除实验性的 AOT 和 JIT 编译器

411: Deprecate the Security Manager for Removal 弃用即将删除的安全管理器

412: Foreign Function & Memory API (Incubator) 外部函数和内存 API(孵化特性)

414: Vector API (Second Incubator) Vector API(第二次孵化特性)

415: Context-Specific Deserialization Filters 上下文特定的反序列化过滤器

# 1.5 JDK 各版本下载链接

https://www.oracle.com/java/technologies/downloads/archive/
image-20220525200441935

链接:https://pan.baidu.com/s/15QrBUOvfE9vjlTzN_EeVLg
提取码:yyds

image-20221213235332866

# 1.6 如何学习新特性

对于新特性,我们应该从哪几个角度学习新特性呢?

  • 语法

    • JDK5: 自动装箱与自动拆箱enum泛型 、可变长度参数、foreach 循环等。

    • JDK7:字符串 switch 语句、 try-with-resources语句 、二进制字面量、数字字面量下划线等。

    • JDK8: Lambda 表达式 、函数式接口、接口中的默认方法和静态方法、 Stream API 、新的日期 / 时间 API 等。

    • JDK9:模块化、私有接口方法、Stream API 增强等。

    • JDK10: 局部变量类型推断 、类数据共享等。

    • JDK11:var 修饰符的增强、字符串新增方法、Optional 类增强等。

    • JDK12:Switch 表达式等。

    • JDK13:文本块、Switch 表达式增强等。

    • JDK14:Pattern Matching for instanceof、 record记录类型 等。

    • JDK15:Sealed 类、Text Blocks 增强、ZGC: 低延迟垃圾回收器等。

    • JDK16:记录类型增强、向量 API、JVM 支持 C++ 14 等新特性。

    • JDK17:修改记录类型,扩展嵌套语法、删除废弃的 GC 策略等。

  • API

    • JDK5:Java.util.concurrent 包、Scanner 类、 StringBuilder类 等。

    • JDK6:Desktop 类、支持脚本语言等新特性。

    • JDK7:Fork/Join 框架、ConcurrentLinkedDeque 类等。

    • JDK8:Stream、Optional、新的日期时间 java.time 包、HashMap 的底层结构、CompletableFuture 类、重复注解、元注解类型等。

    • JDK9:String 的底层结构、Flow API 接口、Reactive Streams、Collection 工厂方法、Stream API 增强等。

    • JDK10:Local-Variable Type Inferenc、Optional.orElseThrow ()、String.lines () 等。

    • JDK11:HttpClient 类、反序列化过滤器、ZGC 垃圾收集器等。

    • JDK12:合并 switch 语句、向量 API 等

    • JDK13:ZGC 增强、Embarrassingly parallel algorithms 等。

    • JDK14: Records类 、Pattern matching for instance of 等。

    • JDK15:异步关闭 Socket 或 ServerSocket、UNIX 域套接字等。

    • JDK16:预期 NullPointerException 的更多信息、Unsafe 等。

    • JDK17:由虚拟机接管的 HTTP/1.1 客户端实现,弱引用垃圾回收、G1 垃圾回收优化等。

  • 底层优化

    • JDK6:支持更高效的 JIT 编译器,启用了压缩指针。

    • JDK7: G1垃圾收集器 、新的类装载器等。

    • JDK8:使用了 Metaspace(元空间)代替永久代;改进了 LinkedHashMap 和 ConcurrentHashMap 等数据结构的实现方式;新的 JS 执行引擎;

    • JDK9:引入 JIT 编译服务、支持分层编译等技术。

    • JDK10:改进的 G1 收集器吞吐量、增量排序功能、allocating-thread-local 功能。

    • JDK11:停用 G1 CMS 垃圾回收器、JVM 动态 Class-Data sharing、现在支持 Epsilon 垃圾回收器等。

    • JDK12:G1 停顿时间预测、64 位 Windows 上的 CDS 内存映射文件等。

    • JDK13:ZGC 的进一步改进、Shenandoah 垃圾回收器的实验特性。

    • JDK14:ZGC 并发线程数限制、稳定化的 ZGC 垃圾回收器。

    • JDK15:大幅提升 JVM 垃圾收集器的性能、再次发布基于标志位的 VM 内部版本号等。

    • JDK16:Java 方法加载相关的优化等。

    • JDK17:删除了已废弃的 ParallelScavenge + SerialOld 组合等。

# 2. Java8 新特性:Lambda 表达式

# 2.1 关于 Java8 新特性简介

Java 8 (又称为 JDK 8 或 JDK1.8) 是 Java 语言开发的一个主要版本。 Java 8 是 oracle 公司于 2014 年 3 月发布,可以看成是自 Java 5 以来 最具革命性的版本 。Java 8 为 Java 语言、编译器、类库、开发工具与 JVM 带来了大量新特性。

image-20220525201653599
  • 速度更快

    • HashMap 的底层数据结构,在数组 + 单向链表的基础上,新增了红黑树
  • 代码更少 (增加了新的语法:Lambda 表达式)

  • 强大的 Stream API

  • 便于并行

    • 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
    • Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel()sequential() 在并行流与顺序流之间进行切换。
  • 最大化减少空指针异常: Optional类

  • Nashorn 引擎,允许在 JVM 上运行 JS 应用

    • 发音 “nass-horn”,是德国二战时一个坦克的命名
    • javascript 运行在 jvm 已经不是新鲜事了,Rhino 早在 jdk6 的时候已经存在。现在替代 Rhino,官方的解释是 Rhino 相比其他 JavaScript 引擎(比如 google 的 V8)实在太慢了,改造 Rhino 还不如重写。所以 Nashorn 的性能也是其一个亮点。
    • Nashorn 项目在 JDK 9 中得到改进;在 JDK11 中 Deprecated ,后续 JDK15 版本中 remove 。在 JDK11 中取以代之的是 GraalVM。(GraalVM 是一个运行时平台,它支持 Java 和其他基于 Java 字节码的语言,但也支持其他语言,如 JavaScript,Ruby,Python 或 LLVM。性能是 Nashorn 的 2 倍以上。)

# 2.2 冗余的匿名内部类

当需要启动一个线程去完成任务时,通常会通过 java.lang.Runnable 接口来定义任务内容,并使用 java.lang.Thread 类来启动该线程。代码如下:

package com.atguigu.fp;
public class UseFunctionalProgramming {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("多线程任务执行!");
            }
        }).start(); // 启动线程
    }
}

本着 “一切皆对象” 的思想,这种做法是无可厚非的:首先创建一个 Runnable 接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。

代码分析:

对于 Runnable 的匿名内部类用法,可以分析出几点内容:

  • Thread 类需要 Runnable 接口作为参数,其中的抽象 run 方法是用来指定线程任务内容的核心;
  • 为了指定 run 的方法体,不得不需要 Runnable 接口的实现类;
  • 为了省去定义一个 RunnableImpl 实现类的麻烦,不得不使用匿名内部类;
  • 必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错;
  • 而实际上,似乎只有方法体才是关键所在

# 2.3 好用的 lambda 表达式

lambda表达式

image-20221111213355625

# 2.4 Lambda 及其使用举例

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升。

  • 从匿名类到 Lambda 的转换举例 1
image-20220527101737072
  • 从匿名类到 Lambda 的转换举例 2
image-20220527101814203

# 2.5 语法

Lambda 表达式:在 Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “ -> ” , 该操作符被称为 Lambda 操作符箭头操作符

格式形参列表 -> 抽象方法的实现方法体

  • 形参列表:
    • 类型都可以省略
    • 如果形参只有一个,则 () 也可以省略
  • 抽象方法的实现方法体:
    • 如果只有一行执行语句,则 {} 可以省略,若有 return,则一并省略

本质

  • 一个匿名函数

  • 函数式接口(Functional Interface)的(实现类的)实例对象

    即 ** 只包含一个抽象方法 ** 的接口

使用条件

  • 必须使用 Java 8 或更高版本。

  • 函数接口(Functional Interface):Lambda 表达式只能用于 **接口中有且仅有一个抽象方法** 的函数接口(Functional Interface),如 RunnableComparator 等。可以使用注解 @FunctionalInterface 来强制检查接口是否为函数接口。

  • Lambda 表达式必须与函数接口的抽象方法的参数和返回类型相匹配。

  • Lambda 表达式主体语句可以是一条表达式或一段代码块(即多条语句)。如果是代码块,需要使用大括号 {} 包围,并且需要使用 return 语句来返回值(如果有返回值的话)。

** 语法格式一:** 无参,无返回值

@Test
public void test1(){
    // 未使用 Lambda 表达式
    Runnable r1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("我爱北京天安门");
        }
    };
    r1.run();
    System.out.println("***********************");
    // 使用 Lambda 表达式
    Runnable r2 = () -> {
        System.out.println("我爱北京故宫");
    };
    r2.run();
}

** 语法格式二:**Lambda 需要一个参数,但是没有返回值。

@Test
public void test2(){
    // 未使用 Lambda 表达式
    Consumer<String> con = new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    };
    con.accept("谎言和誓言的区别是什么?");
    System.out.println("*******************");
    // 使用 Lambda 表达式
    Consumer<String> con1 = (String s) -> {
        System.out.println(s);
    };
    con1.accept("一个是听得人当真了,一个是说的人当真了");
}

语法格式三:数据类型可以省略,因为可由编译器推断得出,称为 “类型推断

@Test
public void test3(){
    // 语法格式三使用前
    Consumer<String> con1 = (String s) -> {
        System.out.println(s);
    };
    con1.accept("一个是听得人当真了,一个是说的人当真了");
    System.out.println("*******************");
    // 语法格式三使用后
    Consumer<String> con2 = (s) -> {
        System.out.println(s);
    };
    con2.accept("一个是听得人当真了,一个是说的人当真了");
}

** 语法格式四:**Lambda 若只需要一个参数时,参数的小括号可以省略

@Test
public void test4(){
    // 语法格式四使用前
    Consumer<String> con1 = (s) -> {
        System.out.println(s);
    };
    con1.accept("一个是听得人当真了,一个是说的人当真了");
    System.out.println("*******************");
    // 语法格式四使用后
    Consumer<String> con2 = s -> {
        System.out.println(s);
    };
    con2.accept("一个是听得人当真了,一个是说的人当真了");
}

** 语法格式五:**Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值

@Test
public void test5(){
    // 语法格式五使用前
    Comparator<Integer> com1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        }
    };
    System.out.println(com1.compare(12,21));
    System.out.println("*****************************");
    // 语法格式五使用后
    Comparator<Integer> com2 = (o1,o2) -> {
        System.out.println(o1);
        System.out.println(o2);
        return o1.compareTo(o2);
    };
    System.out.println(com2.compare(12,6));
}

** 语法格式六:** 当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略

@Test
public void test6(){
    // 语法格式六使用前
    Comparator<Integer> com1 = (o1,o2) -> {
        return o1.compareTo(o2);
    };
    System.out.println(com1.compare(12,6));
    System.out.println("*****************************");
    // 语法格式六使用后
    Comparator<Integer> com2 = (o1,o2) -> o1.compareTo(o2);
    System.out.println(com2.compare(12,21));
}
@Test
public void test7(){
    // 语法格式六使用前
    Consumer<String> con1 = s -> {
        System.out.println(s);
    };
    con1.accept("一个是听得人当真了,一个是说的人当真了");
    System.out.println("*****************************");
    // 语法格式六使用后
    Consumer<String> con2 = s -> System.out.println(s);
    con2.accept("一个是听得人当真了,一个是说的人当真了");
}

# 2.6 关于类型推断

在语法格式三 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “ 类型推断 ”。

image-20220527103215741

举例:

@Test
public void test() {
    // 类型推断 1
    ArrayList<String> list = new ArrayList<>();
    // 类型推断 2
    int[] arr = {1, 2, 3};
}

# 3. Java8 新特性:函数式 (Functional) 接口

# 3.1 什么是函数式接口

  • 只包含一个抽象方法 (Single Abstract Method,简称 SAM)的接口,称为函数式接口。当然该接口 可以包含其他非抽象方法
  • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常 (即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
  • java.util.function 包下定义了 Java 8 的丰富的函数式接口

# 3.2 如何理解函数式接口

  • Java 从诞生日起就是一直倡导 “一切皆对象”,在 Java 里面面向对象 (OOP) 编程是一切。但是随着 python、scala 等语言的兴起和新技术的挑战,Java 不得不做出调整以便支持更加广泛的技术要求,即 Java 不但可以支持 OOP 还可以支持 OOF(面向函数编程)
    • Java8 引入了 Lambda 表达式之后,Java 也开始支持函数式编程。
    • Lambda 表达式不是 Java 最早使用的。目前 C++,C#,Python,Scala 等均支持 Lambda 表达式。
  • 面向对象的思想:
    • 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。
  • 函数式编程思想:
    • 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。
  • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda 表达式的类型是函数。但是在 Java8 中,有所不同。在 Java8 中,Lambda 表达式是对象,而不是函数,它们必须依附于一类特别的对象类型 ———— 函数式接口
  • 简单的说,在 Java8 中,Lambda 表达式就是一个函数式接口的(实现类的)实例。这就是 Lambda 表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用 Lambda 表达式来表示。

# 3.3 举例

举例 1:

image-20220527111442115

举例 2:

image-20220527111621424

作为参数传递 Lambda 表达式:

image-20220527111751485

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收 Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

# 3.4 Java 内置函数式接口

JDK8 中声明的函数式接口都在 ** java.util.function包 ** 下。

# 3.4.1 之前的函数式接口

之前学过的接口,有些就是函数式接口,比如:

  • java.lang.Runnable
    • public void run()
  • java.lang.Iterable<T>
    • public Iteratoriterate()
  • java.lang.Comparable<T>
    • public int compareTo(T t)
  • java.util.Comparator<T>
    • public int compare(T t1, T t2)

# 3.4.2 四大核心函数式接口

称谓函数式接口参数类型用途
消费 型接口Consumer<T>T对类型为 T 的对象应用操作,包含方法: void accept(T t)
供给 型接口Supplier<T>返回类型为 T 的对象,包含方法: T get()
函数 型接口Function<T, R>T对类型为 T 的对象应用操作,并返回结果。结果是 R 类型的对象。包含方法: R apply(T t)
判断 型接口Predicate<T>T确定类型为 T 的对象是否满足某约束,并返回 boolean 值。包含方法: boolean test(T t)

# 3.4.3 其它接口

类型 1:消费型接口

消费型接口的抽象方法特点:有形参,但是返回值类型是 void

接口名抽象方法描述
BiConsumer<T,U>void accept(T t, U u)接收两个对象用于完成功能
DoubleConsumervoid accept(double value)接收一个 double 值
IntConsumervoid accept(int value)接收一个 int 值
LongConsumervoid accept(long value)接收一个 long 值
ObjDoubleConsumervoid accept(T t, double value)接收一个对象和一个 double 值
ObjIntConsumervoid accept(T t, int value)接收一个对象和一个 int 值
ObjLongConsumervoid accept(T t, long value)接收一个对象和一个 long 值

类型 2:供给型接口

这类接口的抽象方法特点:无参,但是有返回值

接口名抽象方法描述
BooleanSupplierboolean getAsBoolean()返回一个 boolean 值
DoubleSupplierdouble getAsDouble()返回一个 double 值
IntSupplierint getAsInt()返回一个 int 值
LongSupplierlong getAsLong()返回一个 long 值

类型 3:函数型接口

这类接口的抽象方法特点:既有参数又有返回值

接口名抽象方法描述
UnaryOperatorT apply(T t)接收一个 T 类型对象,返回一个 T 类型对象结果
DoubleFunctionR apply(double value)接收一个 double 值,返回一个 R 类型对象
IntFunctionR apply(int value)接收一个 int 值,返回一个 R 类型对象
LongFunctionR apply(long value)接收一个 long 值,返回一个 R 类型对象
ToDoubleFunctiondouble applyAsDouble(T value)接收一个 T 类型对象,返回一个 double
ToIntFunctionint applyAsInt(T value)接收一个 T 类型对象,返回一个 int
ToLongFunctionlong applyAsLong(T value)接收一个 T 类型对象,返回一个 long
DoubleToIntFunctionint applyAsInt(double value)接收一个 double 值,返回一个 int 结果
DoubleToLongFunctionlong applyAsLong(double value)接收一个 double 值,返回一个 long 结果
IntToDoubleFunctiondouble applyAsDouble(int value)接收一个 int 值,返回一个 double 结果
IntToLongFunctionlong applyAsLong(int value)接收一个 int 值,返回一个 long 结果
LongToDoubleFunctiondouble applyAsDouble(long value)接收一个 long 值,返回一个 double 结果
LongToIntFunctionint applyAsInt(long value)接收一个 long 值,返回一个 int 结果
DoubleUnaryOperatordouble applyAsDouble(double operand)接收一个 double 值,返回一个 double
IntUnaryOperatorint applyAsInt(int operand)接收一个 int 值,返回一个 int 结果
LongUnaryOperatorlong applyAsLong(long operand)接收一个 long 值,返回一个 long 结果
BiFunction<T,U,R>R apply(T t, U u)接收一个 T 类型和一个 U 类型对象,返回一个 R 类型对象结果
BinaryOperatorT apply(T t, T u)接收两个 T 类型对象,返回一个 T 类型对象结果
ToDoubleBiFunction<T,U>double applyAsDouble(T t, U u)接收一个 T 类型和一个 U 类型对象,返回一个 double
ToIntBiFunction<T,U>int applyAsInt(T t, U u)接收一个 T 类型和一个 U 类型对象,返回一个 int
ToLongBiFunction<T,U>long applyAsLong(T t, U u)接收一个 T 类型和一个 U 类型对象,返回一个 long
DoubleBinaryOperatordouble applyAsDouble(double left, double right)接收两个 double 值,返回一个 double 结果
IntBinaryOperatorint applyAsInt(int left, int right)接收两个 int 值,返回一个 int 结果
LongBinaryOperatorlong applyAsLong(long left, long right)接收两个 long 值,返回一个 long 结果

类型 4:判断型接口

这类接口的抽象方法特点:有参,但是返回值类型是 boolean 结果。

接口名抽象方法描述
BiPredicate<T,U>boolean test(T t, U u)接收两个对象
DoublePredicateboolean test(double value)接收一个 double 值
IntPredicateboolean test(int value)接收一个 int 值
LongPredicateboolean test(long value)接收一个 long 值

# 3.4.4 内置接口代码演示

举例 1:

package com.atguigu.four;
import java.util.Arrays;
import java.util.List;
public class TestConsumer {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("java","c","python","c++","VB","C#");
        // 遍历 Collection 集合,并将传递给 action 参数的操作代码应用在每一个元素上。
        list.forEach(s -> System.out.println(s));
    }
}

举例 2:

package com.atguigu.four;
import java.util.function.Supplier;
public class TestSupplier {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "尚硅谷";
        System.out.println(supplier.get());
    }
}

举例 3:

package com.atguigu.four;
import java.util.ArrayList;
public class TestPredicate {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("atguigu");
        list.add("ok");
        list.add("yes");
        System.out.println("删除之前:");
        list.forEach(t-> System.out.println(t));
		
        // 用于删除集合中满足 filter 指定的条件判断的。
        // 删除包含 o 字母的元素
        list.removeIf(s -> s.contains("o"));
        System.out.println("删除包含o字母的元素之后:");
        list.forEach(t-> System.out.println(t));
    }
}

举例 4:

package com.atguigu.four;
import java.util.function.Function;
public class TestFunction {
    public static void main(String[] args) {
        // 使用 Lambda 表达式实现 Function<T,R > 接口,可以实现将一个字符串首字母转为大写的功能。
        Function<String,String> fun = s -> s.substring(0,1).toUpperCase() + s.substring(1);
        System.out.println(fun.apply("hello"));
    }
}

# 3.4.5 练习

练习 1:无参无返回值形式

假如有自定义函数式接口 Call 如下:

public interface Call {
    void shout();
}

在测试类中声明一个如下方法:

public static void callSomething(Call call){
		call.shout();
}

在测试类的 main 方法中调用 callSomething 方法,并用 Lambda 表达式为形参 call 赋值,可以喊出任意你想说的话。

public class TestLambda {
	public static void main(String[] args) {
		callSomething(()->System.out.println("回家吃饭"));
		callSomething(()->System.out.println("我爱你"));
		callSomething(()->System.out.println("滚蛋"));
		callSomething(()->System.out.println("回来"));
	}
	public static void callSomething(Call call){
		call.shout();
	}
}
interface Call {
    void shout();
}

练习 2:消费型接口

代码示例:Consumer接口

在 JDK1.8 中 Collection 集合接口的父接口 Iterable 接口中增加了一个默认方法:

public default void forEach(Consumer<? super T> action) 遍历 Collection 集合的每个元素,执行 “xxx 消费型” 操作。

在 JDK1.8 中 Map 集合接口中增加了一个默认方法:

public default void forEach(BiConsumer<? super K,? super V> action) 遍历 Map 集合的每对映射关系,执行 “xxx 消费型” 操作。

案例:

(1)创建一个 Collection 系列的集合,添加一些字符串,调用 forEach 方法遍历查看

(2)创建一个 Map 系列的集合,添加一些 (key,value) 键值对,调用 forEach 方法遍历查看

示例代码:

@Test
	public void test1(){
		List<String> list = Arrays.asList("hello","java","lambda","atguigu");
		list.forEach(s -> System.out.println(s));
    }
	@Test
	public void test2(){
		HashMap<Integer,String> map = new HashMap<>();
		map.put(1, "hello");
		map.put(2, "java");
		map.put(3, "lambda");
		map.put(4, "atguigu");
		map.forEach((k,v) -> System.out.println(k+"->"+v));
	}

练习 3:供给型接口

代码示例:Supplier接口

在 JDK1.8 中增加了 StreamAPI,java.util.stream.Stream是一个数据流。这个类型有一个静态方法:

public static <T> Stream<T> generate(Supplier<T> s) 可以创建 Stream 的对象。而又包含一个 forEach 方法可以遍历流中的元素: public void forEach(Consumer<? super T> action)

案例:

现在请调用 Stream 的 generate 方法,来产生一个流对象,并调用 Math.random () 方法来产生数据,为 Supplier 函数式接口的形参赋值。最后调用 forEach 方法遍历流中的数据查看结果。

@Test
	public void test2(){
		Stream.generate(() -> Math.random()).forEach(num -> System.out.println(num));
	}

练习 4:功能型接口

代码示例:Function<T,R> 接口

在 JDK1.8 时 Map 接口增加了很多方法,例如:

public default void replaceAll(BiFunction<? super K,? super V,? extends V> function) 按照 function 指定的操作替换 map 中的 value。

public default void forEach(BiConsumer<? super K,? super V> action) 遍历 Map 集合的每对映射关系,执行 “xxx 消费型” 操作。

案例:

(1)声明一个 Employee 员工类型,包含编号、姓名、薪资。

(2)添加 n 个员工对象到一个 HashMap<Integer,Employee> 集合中,其中员工编号为 key,员工对象为 value。

(3)调用 Map 的 forEach 遍历集合

(4)调用 Map 的 replaceAll 方法,将其中薪资低于 10000 元的,薪资设置为 10000。

(5)再次调用 Map 的 forEach 遍历集合查看结果

Employee 类:

class Employee{
	private int id;
	private String name;
	private double salary;
	public Employee(int id, String name, double salary) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
	}
	public Employee() {
		super();
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
	}
	
}

测试类:

import java.util.HashMap;
public class TestLambda {
	public static void main(String[] args) {
		HashMap<Integer,Employee> map = new HashMap<>();
		Employee e1 = new Employee(1, "张三", 8000);
		Employee e2 = new Employee(2, "李四", 9000);
		Employee e3 = new Employee(3, "王五", 10000);
		Employee e4 = new Employee(4, "赵六", 11000);
		Employee e5 = new Employee(5, "钱七", 12000);
		
		map.put(e1.getId(), e1);
		map.put(e2.getId(), e2);
		map.put(e3.getId(), e3);
		map.put(e4.getId(), e4);
		map.put(e5.getId(), e5);
		
		map.forEach((k,v) -> System.out.println(k+"="+v));
		System.out.println();
		
		map.replaceAll((k,v)->{
			if(v.getSalary()<10000){
				v.setSalary(10000);
			}
			return v;
		});
		map.forEach((k,v) -> System.out.println(k+"="+v));
	}
}

练习 5:判断型接口

代码示例:Predicate接口

JDK1.8 时,Collecton接口增加了一下方法,其中一个如下:

public default boolean removeIf(Predicate<? super E> filter) 用于删除集合中满足 filter 指定的条件判断的。

public default void forEach(Consumer<? super T> action) 遍历 Collection 集合的每个元素,执行 “xxx 消费型” 操作。

案例:

(1)添加一些字符串到一个 Collection 集合中

(2)调用 forEach 遍历集合

(3)调用 removeIf 方法,删除其中字符串的长度 < 5 的

(4)再次调用 forEach 遍历集合

import java.util.ArrayList;
public class TestLambda {
	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<>();
		list.add("hello");
		list.add("java");
		list.add("atguigu");
		list.add("ok");
		list.add("yes");
		
		list.forEach(str->System.out.println(str));
		System.out.println();
		
		list.removeIf(str->str.length()<5);
		list.forEach(str->System.out.println(str));
	}
}

练习 6:判断型接口

案例:

(1)声明一个 Employee 员工类型,包含编号、姓名、性别,年龄,薪资。

(2)声明一个 EmployeeSerice 员工管理类,包含一个 ArrayList集合的属性 all,在 EmployeeSerice 的构造器中,创建一些员工对象,为 all 集合初始化。

(3)在 EmployeeSerice 员工管理类中,声明一个方法:ArrayListget(Predicatep),即将满足 p 指定的条件的员工,添加到一个新的 ArrayList集合中返回。

(4)在测试类中创建 EmployeeSerice 员工管理类的对象,并调用 get 方法,分别获取:

  • 所有员工对象
  • 所有年龄超过 35 的员工
  • 所有薪资高于 15000 的女员工
  • 所有编号是偶数的员工
  • 名字是 “张三” 的员工
  • 年龄超过 25,薪资低于 10000 的男员工

示例代码:

Employee 类:

public class Employee{
	private int id;
	private String name;
	private char gender;
	private int age;
	private double salary;
	
	public Employee(int id, String name, char gender, int age, double salary) {
		super();
		this.id = id;
		this.name = name;
		this.gender = gender;
		this.age = age;
		this.salary = salary;
	}
	public Employee() {
		super();
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", gender=" + gender + ", age=" + age + ", salary=" + salary
				+ "]";
	}
}

员工管理类:

class EmployeeService{
	private ArrayList<Employee> all;
	public EmployeeService(){
		all = new ArrayList<Employee>();
		all.add(new Employee(1, "张三", '男', 33, 8000));
		all.add(new Employee(2, "翠花", '女', 23, 18000));
		all.add(new Employee(3, "无能", '男', 46, 8000));
		all.add(new Employee(4, "李四", '女', 23, 9000));
		all.add(new Employee(5, "老王", '男', 23, 15000));
		all.add(new Employee(6, "大嘴", '男', 23, 11000));
	}
	public ArrayList<Employee> get(Predicate<Employee> p){
		ArrayList<Employee> result = new ArrayList<Employee>();
		for (Employee emp : result) {
			if(p.test(emp)){
				result.add(emp);
			}
		}
		return result;
	}
}

测试类:

public class TestLambda {
	public static void main(String[] args) {
		EmployeeService es = new EmployeeService();
		
		es.get(e -> true).forEach(e->System.out.println(e));
		System.out.println();
		es.get(e -> e.getAge()>35).forEach(e->System.out.println(e));
		System.out.println();
		es.get(e -> e.getSalary()>15000 && e.getGender()=='女').forEach(e->System.out.println(e));
		System.out.println();
		es.get(e -> e.getId()%2==0).forEach(e->System.out.println(e));
		System.out.println();
		es.get(e -> "张三".equals(e.getName())).forEach(e->System.out.println(e));
		System.out.println();
		es.get(e -> e.getAge()>25 && e.getSalary()<10000 && e.getGender()=='男').forEach(e->System.out.println(e));
	}
}

# 4. Java8 新特性:方法引用构造器引用

Lambda 表达式是可以简化函数式接口的变量或形参赋值的语法。

方法引用和构造器引用是为了简化 Lambda 表达式的

# 4.1 方法引用

要传递给 Lambda 体的操作,已经有实现的方法了,可以使用方法引用!

方法引用可以看做是 Lambda 表达式深层次的表达。换句话说,方法引用就是 Lambda 表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是 Lambda 表达式的一个语法糖

语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得・约翰・兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法 对语言的功能并没有影响,但是更方便程序员使用 。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

# 4.1.1 方法引用格式

  • 格式:使用方法引用操作符 “ :: ” 将类 (或对象) 与 方法名分隔开来。

    • 两个:中间不能有空格,而且必须英文状态下半角输入
  • 如下三种主要使用情况:

    • 情况 1: 对象 :: 实例方法名
    • 情况 2: 类 :: 静态方法名
    • 情况 3: 类 :: 实例方法名

# 4.1.2 方法引用使用前提

要求 1: Lambda体只有一句语句 ,并且是通过 调用一个对象的/类现有的方法 来完成的

例如:System.out 对象,调用 println () 方法来完成 Lambda 体

​ Math 类,调用 random () 静态方法来完成 Lambda 体

要求 2:

针对情况 1: 对象 :: 实例方法名

  • 函数式接口中的抽象方法 a 的参数列表和返回值类型,与 某一个对象的方法b 的参数列表和返回值类型一致,可以考虑使用方法 b 实现对方法 a 的替换、覆盖。

针对情况 2: 类 :: 静态方法名

  • 函数式接口中的抽象方法 a 的参数列表和返回值类型,与 某一个类的静态方法b 的参数列表和返回值类型一致,可以考虑使用方法 b 实现对方法 a 的替换、覆盖。

针对情况 3: 类 :: 实例方法名

  • 函数式接口中的抽象方法 a 的返回值类型,与 某一个对象的实例方法b 的返回值类型相同
  • 函数式接口中的抽象方法 a 的形参列表有 n 个参数,方法 b 的形参列表有 n-1 个参数
    • 方法 a 的第 1 个参数是方法 b 的调用者
    • 方法 a 的后 n-1 个参数与方法 b 的 n-1 个参数一致(类型相同 / 满足多态)
  • 可以考虑使用方法 b 实现对方法 a 的替换、覆盖。

例如:t->System.out.println (t)

​ () -> Math.random () 都是无参

# 4.1.3 举例

/**
 * ClassName: MethodReferenceTest
 * Package: jdk8.reference
 * Description:
 * 方法引用,可以理解为是 Lambda 表达式的简化写法。
 * - 使用前提:
 * --- 函数式接口中的抽象方法的实现方法体只有一条执行语句
 * --- 函数式接口中的抽象方法的参数列表和返回值类型与方法引用的方法的参数列表和返回值类型一致
 * - 三种语法格式:
 * --- 对象::实例方法名称
 * --- 类::静态方法名称
 * --- 类::实例方法名称
 * - 扩展:
 * --- 1. 构造器引用:ClassName::new
 * --- 2. 数组引用:Type []::new
 *
 * @Author 贺健翔
 * @Create 2023/4/17 14:05
 * @Version 1.0
 */
public class MethodReferenceTest {
    /**
     * 情况一:对象::实例方法名称
     * 要求:函数式接口中的抽象方法 a 的参数列表和返回值类型,与 `某一个对象的方法 b` 的参数列表和返回值类型一致,可以考虑使用方法 b 实现对方法 a 的替换、覆盖。
     * 注意:此方法 b 是实例方法,需要通过对象来调用。
     * 例如:System.out::println
     */
    @Test
    public void test1() {
        // 1. 匿名内部类
        Consumer<String> con1 = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        con1.accept("hello world");
        // 2.lambda 表达式
        Consumer<String> con2 = s -> System.out.println(s);
        con2.accept("hello world");
        // 3. 方法引用
        PrintStream ps = System.out; // 创建 PrintStream 对象
        Consumer<String> con3 = ps::println; // 通过 PrintStream 对象调用 println 方法
        con3.accept("hello world");
    }
    /**
     * 情况二:类::静态方法名称
     * 要求:函数式接口中的抽象方法 a 的参数列表和返回值类型,与 `某一个类的静态方法 b` 的参数列表和返回值类型一致,可以考虑使用方法 b 实现对方法 a 的替换、覆盖。
     * 注意:此方法 b 是静态方法,需要通过类名来调用。
     * 例如:Integer::compare、Math::round
     */
    @Test
    public void test2() {
        // 1. 匿名内部类
        Comparator<Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        };
        System.out.println(com1.compare(12, 21));
        // 2.lambda 表达式
        Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
        System.out.println(com2.compare(12, 21));
        // 3. 方法引用
        Comparator<Integer> com3 = Integer::compare; // 通过类名调用静态方法
        System.out.println(com3.compare(12, 21));
    }
    @Test
    public void test3() {
        // 1. 匿名内部类
        Function<Double, Long> func1 = new Function<Double, Long>() {
            @Override
            public Long apply(Double aDouble) {
                return Math.round(aDouble);
            }
        };
        System.out.println(func1.apply(12.3));
        // 2.lambda 表达式
        Function<Double, Long> func2 = aDouble -> Math.round(aDouble);
        System.out.println(func2.apply(12.3));
        // 3. 方法引用
        Function<Double, Long> func3 = Math::round; // 通过类名调用静态方法
        System.out.println(func3.apply(12.3));
    }
    /**
     * (难点)情况三:类::实例方法名称
     * 要求:
     * - 函数式接口中的抽象方法 a 的返回值类型,与 `某一个对象的实例方法 b` 的返回值类型相同
     * - 函数式接口中的抽象方法 a 的形参列表有 n 个参数,方法 b 的形参列表有 n-1 个参数
     * --- 方法 a 的第 1 个参数是方法 b 的调用者
     * --- 方法 a 的后 n-1 个参数与方法 b 的 n-1 个参数一致(类型相同 / 满足多态)
     * 可以考虑使用方法 b 实现对方法 a 的替换、覆盖。
     * 注意:此方法 b 是实例方法,需要通过对象来调用,但是形式上通过类名来调用。
     * 例如:String::compareTo、String::equals
     */
    @Test
    public void test5() {
        // 1. 匿名内部类
        Comparator<String> com1 = new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return s1.compareTo(s2);
            }
        };
        System.out.println(com1.compare("abc", "abd"));
        // 2.lambda 表达式
        Comparator<String> com2 = (s1, s2) -> s1.compareTo(s2);
        System.out.println(com2.compare("abc", "abd"));
        // 3. 方法引用
        Comparator<String> com3 = String::compareTo; // 通过类名调用静态方法
        System.out.println(com3.compare("abc", "abd"));
    }
    @Test
    public void test6() {
        // 1. 匿名内部类
        BiPredicate<String, String> bp1 = new BiPredicate<String, String>() {
            @Override
            public boolean test(String s1, String s2) {
                return s1.equals(s2);
            }
        };
        System.out.println(bp1.test("abc", "abd"));
        // 2.lambda 表达式
        BiPredicate<String, String> bp2 = (s1, s2) -> s1.equals(s2);
        System.out.println(bp2.test("abc", "abd"));
        // 3. 方法引用
        BiPredicate<String, String> bp3 = String::equals; // 通过类名调用静态方法
        System.out.println(bp3.test("abc", "abd"));
    }
}

# 4.2 构造器引用

当 Lambda 表达式是创建一个对象,并且满足 Lambda 表达式形参,正好是给创建这个对象的构造器的实参列表,就可以使用构造器引用。

格式: 类名::new

说明:

  • 调用了类名对应类中的某个确定的构造器
  • 通过函数式接口的抽象方法的形参列表来确定具体调用哪一个构造器

使用前提:函数式接口中的抽象方法的参数列表要与构造器的参数列表保持一致。

举例:

/**
 * ClassName: ConstructorReferenceTest
 * Package: jdk8.reference
 * Description:
 * 构造器引用。
 * - 格式:类名::new
 * - 使用前提:函数式接口中的抽象方法的参数列表要与构造器的参数列表保持一致。
 *
 * @Author 贺健翔
 * @Create 2023/4/17 15:20
 * @Version 1.0
 */
public class ConstructorReferenceTest {
    // Supplier 中的 T get ()
    @Test
    public void test1() {
        // 1. 匿名内部类
        Supplier<Employee> sup1 = new Supplier<Employee>() {
            @Override
            public Employee get() {
                return new Employee();
            }
        };
        System.out.println(sup1.get());
        // 2.lambda 表达式
        Supplier<Employee> sup2 = () -> new Employee();
        System.out.println(sup2.get());
        // 3. 构造器引用
        Supplier<Employee> sup3 = Employee::new; // 调用的是 Employee 类中无参的构造器
        System.out.println(sup3.get());
    }
    // Function 中的 R apply (T t)
    @Test
    public void test2() {
        // 1. 匿名内部类
        Function<Integer, Employee> fun1 = new Function<Integer, Employee>() {
            @Override
            public Employee apply(Integer id) {
                return new Employee(id);
            }
        };
        System.out.println(fun1.apply(12));
        // 2.lambda 表达式
        Function<Integer, Employee> fun2 = id -> new Employee(id);
        System.out.println(fun2.apply(12));
        // 3. 构造器引用
        Function<Integer, Employee> fun3 = Employee::new; // 调用的是 Employee 类中参数类型是 Integer/int 的构造器
        System.out.println(fun3.apply(12));
    }
    // BiFunction 中的 R apply (T t, U u)
    @Test
    public void test3() {
        // 1. 匿名内部类
        BiFunction<Integer, String, Employee> fun1 = new BiFunction<Integer, String, Employee>() {
            @Override
            public Employee apply(Integer id, String name) {
                return new Employee(id, name);
            }
        };
        System.out.println(fun1.apply(12, "张三"));
        // 2.lambda 表达式
        BiFunction<Integer, String, Employee> fun2 = (id, name) -> new Employee(id, name);
        System.out.println(fun2.apply(12, "张三"));
        // 3. 构造器引用
        BiFunction<Integer, String, Employee> fun3 = Employee::new; // 调用的是 Employee 类中参数类型是 Integer/int 和 String 的构造器
        System.out.println(fun3.apply(12, "张三"));
    }
}
package com.atguigu.java2;
/**
 * @author 尚硅谷 - 宋红康 邮箱:shkstart@126.com
 */
public class Employee {
	private int id;
	private String name;
	private int age;
	private double salary;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public double getSalary() {
		return salary;
	}
	public void setSalary(double salary) {
		this.salary = salary;
	}
	public Employee() {
		System.out.println("Employee().....");
	}
	public Employee(int id) {
		this.id = id;
		System.out.println("Employee(int id).....");
	}
	public Employee(int id, String name) {
		this.id = id;
		this.name = name;
	}
	public Employee(int id, String name, int age, double salary) {
		this.id = id;
		this.name = name;
		this.age = age;
		this.salary = salary;
	}
	@Override
	public String toString() {
		return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
	}
}

# 4.3 数组构造引用

当 Lambda 表达式是创建一个数组对象,并且满足 Lambda 表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用。

格式: 类名[]::new

举例:

public class ArrayReferenceTest {
    // Function 中的 R apply (T t)
    @Test
    public void test1() {
        // 1. 匿名内部类
        Function<Integer, Employee[]> func1 = new Function<Integer, Employee[]>() {
            @Override
            public Employee[] apply(Integer length) {
                return new Employee[length];
            }
        };
        System.out.println(func1.apply(10).length);
        // 2.lambda 表达式
        Function<Integer, Employee[]> func2 = length -> new Employee[length];
        System.out.println(func2.apply(10).length);
        // 3. 数组引用
        Function<Integer, Employee[]> func3 = Employee[]::new;
        System.out.println(func3.apply(10).length);
    }
}

# 5. Java8 新特性:强大的 Stream API

# 5.1 说明

  • Java8 中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。
  • Stream API ( java.util.stream ) 把真正的函数式编程风格引入到 Java 中。这是目前为止对 Java 类库 最好的补充 ,因为 Stream API 可以极大提供 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码
  • Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式

# 5.2 为什么要使用 Stream API

实际开发中,项目中多数数据源都来自于 MySQL、Oracle 等。但现在数据源可以更多了,有 MongDB,Redis 等,而这些 NoSQL 的数据就需要 Java 层面去处理

# 5.3 什么是 Stream

Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

Collection 集合和 Stream 的区别:

  • Collection 是一种静态的内存数据结构,讲的是数据,而 Stream 是有关计算的,讲的是计算。
  • 前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。

注意:

  • Stream 自己不会存储元素

  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。

  • Stream 操作是 **延迟执行** 的。这意味着他们会等到需要结果的时候才执行。即一旦执行终止操作,就执行中间操作链,并产生结果。

  • Stream 一旦执行了终止操作,就不能再调用其它中间操作或终止操作了,需要重新创建 Stream

# 5.4 Stream 的操作三个步骤

1- 实例化(创建 Stream)
通过一个数据源(如:集合、数组)获取一个流

2- 中间操作
每次处理都会返回一个持有结果的新 Stream,即中间操作的方法返回值仍然是 Stream 类型的对象。因此中间操作可以是个 操作链 ,可对数据源的数据进行 n 次处理,但是在终止操作前,并不会真正执行

3- 终止操作 (终端操作)
终止操作的方法返回值类型就不再是 Stream 了,因此一旦执行终止操作,就结束整个 Stream 操作了。一旦执行终止操作,就执行中间操作链,最终产生结果并结束 Stream

image-20220514180803311

# 5.4.1 创建 Stream 实例

方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • default Streamstream () : 返回一个顺序流

  • default StreamparallelStream () : 返回一个并行流

@Test
public void test01(){
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    //JDK1.8 中,Collection 系列集合增加了方法
    Stream<Integer> stream = list.stream();
}

方式二:通过数组

Java8 中的 Arrays.stream(T[] array) 可以获取数组流,这里的 T 既可以是引用数据类型,也可以是基本数据类型:

  • staticStreamstream (T [] array): 返回一个流
  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)
@Test
public void test02(){
    String[] arr = {"hello","world"};
    Stream<String> stream = Arrays.stream(arr); 
}
@Test
public void test03(){
    int[] arr = {1,2,3,4,5};
    IntStream stream = Arrays.stream(arr);
}

方式三:通过 Stream 的 of ()

可以调用 Stream.of(T... values) , 通过显示值创建一个流。它可以接收任意数量的参数

  • public staticStreamof (T... values) : 返回一个流
@Test
public void test04(){
    Stream<Integer> stream = Stream.of(1,2,3,4,5);
    stream.forEach(System.out::println);
}

方式四:创建无限流 (了解)

可以使用静态方法 Stream.iterate(final T seed, final UnaryOperator<T> f)Stream.generate(Supplier<T> s) , 创建无限流。

  • 迭代
    public staticStreamiterate(final T seed, final UnaryOperatorf)

  • 生成
    public staticStreamgenerate(Suppliers)

// 方式四:创建无限流
@Test
public void test05() {
	// 迭代
	// public static<T> Stream<T> iterate(final T seed, final
	// UnaryOperator<T> f)
	Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
	stream.limit(10).forEach(System.out::println);
	// 生成
	// public static<T> Stream<T> generate(Supplier<T> s)
	Stream<Double> stream1 = Stream.generate(Math::random);
	stream1.limit(10).forEach(System.out::println);
}

# 5.4.2 一系列中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为 “惰性求值”

  1. 筛选与切片
方 法描 述
filter(Predicatep)接收 Lambda,从流中过滤某些元素
distinct()筛选,通过流所生成元素的 hashCode () 和 equals ()去重
limit(long maxSize)截断流,使其元素不超过给定数量
skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。
若流中元素不足 n 个,则返回一个空流。与 limit (n) 互补
  1. 映 射
方法描述
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
  1. 排 序
方法描述
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator com)产生一个新流,其中按比较器顺序排序

代码举例:

package com.atguigu.stream;
import org.junit.Test;
import java.util.Arrays;
import java.util.stream.Stream;
public class StreamMiddleOperate {
	@Test
    public void test01(){
        //1、创建 Stream
        Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
        //2、加工处理
        // 过滤:filter (Predicate p)
        // 把里面的偶数拿出来
        /*
         * filter (Predicate p)
         * Predicate 是函数式接口,抽象方法:boolean test (T t)
         */
        stream = stream.filter(t -> t%2==0);
        //3、终结操作:例如:遍历
        stream.forEach(System.out::println);
    }
    @Test
    public void test02(){
        Stream.of(1,2,3,4,5,6)
                .filter(t -> t%2==0)
                .forEach(System.out::println);
    }
    @Test
    public void test03(){
        Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                .distinct()
                .forEach(System.out::println);
    }
    @Test
    public void test04(){
        Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                .limit(3)
                .forEach(System.out::println);
    }
    @Test
    public void test05(){
        Stream.of(1,2,2,3,3,4,4,5,2,3,4,5,6,7)
                .distinct()  //(1,2,3,4,5,6,7)
                .filter(t -> t%2!=0) //(1,3,5,7)
                .limit(3)
                .forEach(System.out::println);
    }
    @Test
    public void test06(){
        Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                .skip(5)
                .forEach(System.out::println);
    }
    @Test
    public void test07(){
        Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                .skip(5)
                .distinct()
                .filter(t -> t%3==0)
                .forEach(System.out::println);
    }
    @Test
    public void test08(){
        long count = Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
                .distinct()
                .peek(System.out::println)  //Consumer 接口的抽象方法  void accept (T t)
                .count();
        System.out.println("count="+count);
    }
    @Test
    public void test09(){
        // 希望能够找出前三个最大值,前三名最大的,不重复
        Stream.of(11,2,39,4,54,6,2,22,3,3,4,54,54)
                .distinct()
                .sorted((t1,t2) -> -Integer.compare(t1, t2))//Comparator 接口  int compare (T t1, T t2)
                .limit(3)
                .forEach(System.out::println);
    }
    @Test
    public void test10(){
        Stream.of(1,2,3,4,5)
                .map(t -> t+=1)//Function<T,R > 接口抽象方法 R apply (T t)
                .forEach(System.out::println);
    }
    @Test
    public void test11(){
        String[] arr = {"hello","world","java"};
        Arrays.stream(arr)
                .map(t->t.toUpperCase())
                .forEach(System.out::println);
    }
    @Test
    public void test12(){
        String[] arr = {"hello","world","java"};
        Arrays.stream(arr)
                .flatMap(t -> Stream.of(t.split("|")))//Function<T,R > 接口抽象方法 R apply (T t)  现在的 R 是一个 Stream
                .forEach(System.out::println);
    } 
}

# 5.4.3 终止操作

  • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

  • 流进行了终止操作后,不能再次使用,需要重新创建 Stream。

  1. 匹配与查找
方法描述
allMatch(Predicate p)检查是否匹配所有元素
**anyMatch(Predicate p) **检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代 (使用 Collection 接口需要用户去做迭代,称为外部迭代。
相反,Stream API 使用内部迭代 —— 它帮你把迭代做了)

到目前为止,针对 List 的遍历方式:

  • Iterator
  • 增强 for
  • 一般 for
  • forEach()
  1. 归约
方法描述
reduce(T identity, BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值。返回 Optional

备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

  1. 收集
方 法描 述
collect(Collector c)将流转换为其他形式。接收一个 Collector 接口 的实现,用于给 Stream 中元素做 **汇总** 的方法

Collector 接口中方法的实现决定了如何对流执行收集的操作 (如收集到 List、Set、Map)。

另外, Collectors 实用类 提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:

方法返回类型作用
toListCollector<T, ?, List>把流中元素收集到 List
List<Employee> emps= list.stream().collect(Collectors.toList());
方法返回类型作用
toSetCollector<T, ?, Set>把流中元素收集到 Set
Set<Employee> emps= list.stream().collect(Collectors.toSet());
方法返回类型作用
toCollectionCollector<T, ?, C>把流中元素收集到创建的集合
Collection<Employee> emps =list.stream().collect(Collectors.toCollection(ArrayList::new));
方法返回类型作用
countingCollector<T, ?, Long>计算流中元素的个数
long count = list.stream().collect(Collectors.counting());
方法返回类型作用
summingIntCollector<T, ?, Integer>对流中元素的整数属性求和
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
方法返回类型作用
averagingIntCollector<T, ?, Double>计算流中元素 Integer 属性的平均值
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
方法返回类型作用
summarizingIntCollector<T, ?, IntSummaryStatistics>收集流中 Integer 属性的统计值。如:平均值
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
方法返回类型作用
joiningCollector<CharSequence, ?, String>连接流中每个字符串
String str= list.stream().map(Employee::getName).collect(Collectors.joining());
方法返回类型作用
maxByCollector<T, ?, Optional>根据比较器选择最大值
Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
方法返回类型作用
minByCollector<T, ?, Optional>根据比较器选择最小值
Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
方法返回类型作用
reducingCollector<T, ?, Optional>从一个作为累加器的初始值开始,利用 BinaryOperator 与流中元素逐个结合,从而归约成单个值
int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
方法返回类型作用
collectingAndThenCollector<T,A,RR>包裹另一个收集器,对其结果转换函数
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
方法返回类型作用
groupingByCollector<T, ?, Map<K, List>>根据某属性值对流分组,属性为 K,结果为 V
Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
方法返回类型作用
partitioningByCollector<T, ?, Map<Boolean, List>>根据 true 或 false 进行分区
Map<Boolean,List<Emp>> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));

举例:

package com.atguigu.stream;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
public class StreamEndding {
    @Test
    public void test01(){
        Stream.of(1,2,3,4,5)
                .forEach(System.out::println);
    }
    @Test
    public void test02(){
        long count = Stream.of(1,2,3,4,5)
                .count();
        System.out.println("count = " + count);
    }
    @Test
    public void test03(){
        boolean result = Stream.of(1,3,5,7,9)
                .allMatch(t -> t%2!=0);
        System.out.println(result);
    }
	@Test
    public void test04(){
        boolean result = Stream.of(1,3,5,7,9)
                .anyMatch(t -> t%2==0);
        System.out.println(result);
    }
	@Test
    public void test05(){
        Optional<Integer> opt = Stream.of(1,3,5,7,9).findFirst();
        System.out.println(opt);
    }
	@Test
    public void test06(){
        Optional<Integer> opt = Stream.of(1,2,3,4,5,7,9)
                .filter(t -> t%3==0)
                .findFirst();
        System.out.println(opt);
    }
	@Test
    public void test07(){
        Optional<Integer> opt = Stream.of(1,2,4,5,7,8)
                .filter(t -> t%3==0)
                .findFirst();
        System.out.println(opt);
    }
    @Test
    public void test08(){
        Optional<Integer> max = Stream.of(1,2,4,5,7,8)
                .max((t1,t2) -> Integer.compare(t1, t2));
        System.out.println(max);
    }
    @Test
    public void test09(){
        Integer reduce = Stream.of(1,2,4,5,7,8)
                .reduce(0, (t1,t2) -> t1+t2);//BinaryOperator 接口   T apply (T t1, T t2)
        System.out.println(reduce);
    }
    @Test
    public void test10(){
        Optional<Integer> max = Stream.of(1,2,4,5,7,8)
                .reduce((t1,t2) -> t1>t2?t1:t2);//BinaryOperator 接口   T apply (T t1, T t2)
        System.out.println(max);
    }
    @Test
    public void test11(){
        List<Integer> list = Stream.of(1,2,4,5,7,8)
                .filter(t -> t%2==0)
                .collect(Collectors.toList());
        System.out.println(list);
    }   
}

# 5.5 Java9 新增 API

新增 1:Stream 实例化方法

ofNullable () 的使用:

Java 8 中 Stream 不能完全为 null,否则会报空指针异常。而 Java 9 中的 ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空 Stream。

// 报 NullPointerException
//Stream<Object> stream1 = Stream.of(null);
//System.out.println(stream1.count());
// 不报异常,允许通过
Stream<String> stringStream = Stream.of("AA", "BB", null);
System.out.println(stringStream.count());//3
// 不报异常,允许通过
List<String> list = new ArrayList<>();
list.add("AA");
list.add(null);
System.out.println(list.stream().count());//2
//ofNullable ():允许值为 null
Stream<Object> stream1 = Stream.ofNullable(null);
System.out.println(stream1.count());//0
Stream<String> stream = Stream.ofNullable("hello world");
System.out.println(stream.count());//1

iterator () 重载的使用:

// 原来的控制终止方式:
Stream.iterate(1,i -> i + 1).limit(10).forEach(System.out::println);
// 现在的终止方式:
Stream.iterate(1,i -> i < 100,i -> i + 1).forEach(System.out::println);

# 5.6 练习

现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,要求使用传统的 for 循环(或增强 for 循环)依次进行以
下若干操作步骤:

  1. 第一个队伍只要名字为 3 个字的成员姓名;存储到一个新集合中。
  2. 第一个队伍筛选之后只要前 3 个人;存储到一个新集合中。
  3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
  4. 第二个队伍筛选之后不要前 2 个人;存储到一个新集合中。
  5. 将两个队伍合并为一个队伍;存储到一个新集合中。
  6. 根据姓名创建 Person 对象;存储到一个新集合中。
  7. 打印整个队伍的 Person 对象信息。

Person 类的代码为:

public class Person {
    private String name;
    public Person() {}
    public Person(String name) {
        this.name = name;
    }    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }
}

两个队伍(集合)的代码如下:

public static void main(String[] args) {
       // 第一支队伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");
        // 第二支队伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵丽颖");
        two.add("张三丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");
    
		//.... 编写代码完成题目要求 
    }

参考答案:

public static void main(String[] args) {
       // 第一支队伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");
    
        // 第二支队伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵丽颖");
        two.add("张三丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");
        
		// 第一个队伍只要名字为 3 个字的成员姓名;
        // 第一个队伍筛选之后只要前 3 个人;
        Stream<String> streamOne = one.stream().filter(s ‐> s.length() == 3).limit(3);
    
        // 第二个队伍只要姓张的成员姓名;
        // 第二个队伍筛选之后不要前 2 个人;
        Stream<String> streamTwo = two.stream().filter(s ‐> s.startsWith("张")).skip(2);
    
        // 将两个队伍合并为一个队伍;
        // 根据姓名创建 Person 对象;
        // 打印整个队伍的 Person 对象信息。
        Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);
        
}