【JAVA】JDK11新特性个人分析(二)

简介: 【JAVA】JDK11新特性个人分析

【JAVA】JDK11新特性个人分析(一)https://developer.aliyun.com/article/1395280

JEP 318: Epsilon: A No-Op Garbage Collector Epsilon 垃圾收集器

首先吸引我的是Epsilon这个单词,和CMS、G1这种带有含义的垃圾收集器名称一样,Epsilon 也有特殊的含义。

这个希腊字母同时也是英语音标ɛ的来源,下面的介绍引自:Ε - 维基百科,自由的百科全书 (wikipedia.org)

Epsilon(大写 Ε、小写 ε 或 ϵ;希腊语:έψιλον;中文音译:伊普西龙、厄普西隆、艾普西龙、艾普塞朗),是第五个希腊字母。Epsilon(ἒ ψιλόν)即“e 简单的、e 单一的”的意思,这是为了与中世纪发生单元音化而变为同音的二合字母 ai(αι,古希腊语:[ai̯])做区别,古典时期本来这个字母读作ei(εἶ,古希腊语:[êː]);源自腓尼基字母 HeHe,又从 epsilon 发展出了拉丁字母 E 和西里尔字母 Е。在希腊数字系统中,E 表示 5。

Epsilon 垃圾收集器用法非常简单:


-XX:+UseEpsilonGC

通常建议搭配参数-XX:+UnlockExperimentalVMOptions 使用。

-XX:+UnlockExperimentalVMOptions : 一般使用在一些低版本jdk想使用高级参数或者可能高版本有的参数情况; 解锁实验参数,允许使用实验性参数,JVM中有些参数不能通过-XX直接复制需要先解锁,比如要使用某些参数的时候,可能不会生效,需要设置这个参数来解锁;

JDK的描述是开发只负责内存分配,其他事情一概不做处理的垃圾收集器,“不回收垃圾的垃圾收集器”看起来怪怪的,因此用途也比较特殊:

  • 性能测试(它可以帮助过滤掉GC引起的性能假象);
  • 内存压力临界点测试(比如知道测试用例应该分配不超过1 GB的内存,我们可以使用-Xmx1g配置-XX:+UseEpsilonGC,如果违反了该约束,则会heap dump并崩溃);
  • 非常短的JOB任务(对于这种任务,接受GC清理堆那都是浪费空间);
  • VM接口测试;
  • Last-drop 延迟&吞吐改进;

这里直接通过一个程序了解Epsilon垃圾收集器的效果:


/**  
 * JDK11 新的垃圾收集器 Epsilon  
 * 需要配置启动参数:  
 * -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC  
 */public class EpsilonGcTest {  
    public static void main(String[] args) {  
        boolean flag = true;  
        List<Garbage> list = new ArrayList<>();  
        long count = 0;  
        while (flag){  
            list.add(new Garbage());  
            if(list.size() == 100000 && count == 0){  
                list.clear();  
                count++;  
            }  
        }  
        System.out.println("程序结束");  
    }  
}  
class Garbage{  
    int n = (int)(Math.random() * 100);  
    @Override  
    protected void finalize() throws Throwable {  
        System.out.println(this + "  : "+ n + "is dying");  
    }  
}

启动之前,不要忘了设置VM Option,如果看不到VM Option这一行,可以点击"Modify options"中添加VM option。

PS:下面的环境变量和program arguments设置启动参数是无效的。

image.png

启动之后一会儿控制台会打印下面的内容并且结束。


Terminating due to java.lang.OutOfMemoryError: Java heap space

如果是传统的垃圾收集器,那么输出的结果将会是System.out.println(this + "  : "+ n + "is dying");的内容,虽然整个程序依然会继续运行直到OOM,但是要花更多的时间等待。


com.zxd.interview.epsilongctest.Garbage@21abe  : 59is dying
com.zxd.interview.epsilongctest.Garbage@6f6b18fc  : 43is dying
com.zxd.interview.epsilongctest.Garbage@701264d8  : 56is dying
com.zxd.interview.epsilongctest.Garbage@5380a941  : 52is dying
com.zxd.interview.epsilongctest.Garbage@43c78e65  : 72is dying
com.zxd.interview.epsilongctest.Garbage@745cd219  : 11is dying
com.zxd.interview.epsilongctest.Garbage@170b4cee  : 68is dying
com.zxd.interview.epsilongctest.Garbage@411725ef  : 22is dying
com.zxd.interview.epsilongctest.Garbage@3edc2f0e  : 4is dying
com.zxd.interview.epsilongctest.Garbage@2c82eed6  : 58is dying
com.zxd.interview.epsilongctest.Garbage@61a3bb94  : 71is dying
com.zxd.interview.epsilongctest.Garbage@19e3d212  : 70is dying
com.zxd.interview.epsilongctest.Garbage@46182a7f  : 19is dying
com.zxd.interview.epsilongctest.Garbage@7dd78834  : 56is dying
com.zxd.interview.epsilongctest.Garbage@1d4e301  : 68is dying

从上面的案例可以看到,有时候不回收垃圾也是有帮助的。

JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (可伸缩低延迟垃圾收集器)

这应该是JDK11最为瞩目的特性, 没有之一, 但是后面带了Experimental, 说明JDK11这一个版本不建议用到生产环境。(JDK13才算是真正完善),ZGC问世对外宣传如下:

  • GC暂停时间不会超过10ms;
  • 即能处理几百兆小堆,也能处理几个T的大堆(OMG);
  • 和G1相比,应用吞吐能力不会下降超过15%;
  • 为未来的GC功能和利用colord指针以及Load barriers优化奠定基础;
  • 初始只支持64位系统;

现代系统可用内存不断增大,GC垃圾收集器同样需要不断进化,现代的应用追求低延迟高吞吐量,ZGC的特点正好切中了GC的痛点。

ZGC的设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。

美团在2020年有一篇关于ZGC的分析文章质量很高: # 新一代垃圾回收器ZGC的探索与实践,如果比较难理解,还可以看看这一篇文章:# 理解并应用JVM垃圾收集器-ZGC

ZGC的了解程度停留在简单理解理论概念即可,下面是从网络整理的资料归档:

GC术语

注意下面的并行、串行、并发更偏向GC的概念:

  • 并行:可以存在多个GC线程同时进行工作,但是无法确定是否需要暂停用户线程
  • 串行:指的是只有单个GC线程进行工作。注意串行也不一定需要暂停用户线程
  • STW:用户线程暂停,此时只有GC线程进行工作。
  • 并发:GC的并发指的是具备并发性,如果说垃圾收集器某些阶段是并发的,那么可以简单在某些阶段GC线程可以和应用程序线程同时进行,但是这需要十分复杂的设计防止用户线程操作导致GC工作无效。
  • 增量阶段指的是某个阶段可以再运行一段时间之后提前中止。

权衡

权衡主要是针对并行和并发阶段带来的问题,并行阶段如果GC占用过多的线程可能导致用户线程性能抖动,而并发阶段如果需要保证能同时处理GC的有效性和用户线程正常工作的问题。

多层堆和压缩

多层堆指的是让JVM管理内存可以像高速缓存一样分为多级缓存存储对象,通过对象分类将频繁访问对象和很少使用对象分开管理的技术。该功能可以通过扩展指针元数据来实现,指针可以实现计数器位并使用该信息来决定是否需要移动对象到较慢的存储上。

压缩是指对象可以以压缩形式保存在内存中,而不是将对象重定位到较慢的存储层。获取压缩对象的时候通过读屏障将其解压并且重新分配。

多重映射

这里不过多介绍计算机的虚拟内存和物理内存概念,简单知道操作系统通过映射表实现了物理内存和虚拟内存的映射,最终通过使用页表和处理器的内存管理单元(MMU)和转换查找缓冲器(TLB)来实现这一点。

而多重映射则指的是多个虚拟内存映射到同一块物理内存的技术。ZGC中使用三个映射来完成多重映射操作。

读屏障

读屏障是每当应用程序线程从堆加载引用时运行的代码片段(即访问对象上的非原生字段(non-primitive field)):

读屏障是JVM向应用代码插入一小段代码的技术。当应用线程从堆中读取对象引用时,就会执行这段代码。需要注意的是,仅“从堆中读取对象引用”才会触发这段代码。


void printName( Person person ) {
  // 这里触发读屏障
  String name = person.name;
  // 因为需要从heap读取引用
  //
  System.out.println(name); // 这里没有直接触发读屏障
}

如注释所说String name = person.name;这一行因为访问了对象非原生字段,将引用加载到本地的name变量,此时操作触发了读屏障。

如前面所说,读屏障是在返回对象引用之前“做手脚”,比较能想到的思路是引用中设置某些“标志位”的方式,但是ZGC实际用的是“测试加载的引用”检查是否满足条件,最后根据结果判断是否执行自己需要的特殊操作。

ZGC 最终选择在读屏障上动手脚,一部分原因是源自Shenadoah收集器的挑战,这个非JDK官方设计的垃圾收集器给Oracle不小的冲击。

ZGC标记

下面简单了解ZGC垃圾回收截断中的标记截断处理,ZGC的标记分为三个阶段:

  • 第一阶段是STW,其中GC roots被标记为活对象。
  • 从roots访问的对象集合称为Live集。GC roots标记步骤非常短,因为roots的总数通常比较小。
  • 第二阶段遍历对象图并标记所有可访问的对象。
  • 读屏障针使用掩码测试所有已加载的引用,该掩码确定它们是否已标记或尚未标记,如果尚未标记引用,则将其添加到队列以进行标记。
  • 第三阶段是边缘情况的处理,这里会有一个非常短暂的STW操作。也是针对经过前两次标记之后依然没有标记完成的最终检查。

image.png

GC roots类似于局部变量,通过它可以访问堆上其他对象。通过Gc root进行可达性分析查找出无法“到达”的对象则被标记为垃圾对象

ZGC 重定位

ZGC在对象清理上采用和G1类似的思路,也就是标记-复制把活对象移动到另一处,然后将只存在垃圾对象的内存释放掉,但是实现方法上更为取巧。

ZGC首先将堆分成许多页面,重定位开始的时候,会挑选一组需要重定位对象的页面,为了保证挑选准确此时需要短暂STW将该集合中root对象的引用映射到新位置。

移动root后下一阶段是并发重定位,此时GC线程遍历重定位集并重新定位其包含的页中所有对象,如果应用线程访问到这些需要重定位对象,则通过转发引用的方式重新定位读取新位置。

这里需要注意读屏障并不能完全保证所有指向旧对象的引用转到重新定位的地址上,在并发的情况下GC需要反复多次检查是否有引用指向旧的位置。

重定位的操作代价十分高昂,为了提高效率会和GC周期的标记阶段一起并行执行,GC周期标记阶段遍历对象对象图的时候,如果发现未重映射的引用则将其重新映射,然后标记为活动状态。

这两步可以抽象认为是一个人负责干活,另一个人负责把前面干活遗留的工作进行检查和标记。

image.png

ZGC 表现

ZGC的SPECjbb 2015吞吐量与Parallel GC(优化吞吐量)大致相当,但平均暂停时间为1ms,最长为4ms。 与之相比G1和Parallel有很多次超过200ms的GC停顿。

ZGC的状态

ZGC 依然有很多亟待解决的问题,以G1为例从发布到支持之间超过3年,ZGC最终到JDK13被正式对外使用。

小结

从整体上看,ZGC的垃圾收集将所有的暂停控制只依赖于GC roots集合上,将停顿控制在一处以达到更好的GC效果。

标记垃圾对象的过程中,标记阶段处理标记终止的最后一次暂停是唯一的例外,但是它是增量的(不一定执行),如果超过GC时间预算,那么GC将恢复到并发标记,直到再次尝试。

删除内容

JEP 320: Remove the Java EE and CORBA Modules

Java EE和CORBA两个模块在JDK9中已经标记"deprecated",在JDK11中正式移除。这部分内容基本上没有Java开发者接触到(连网络搜索都不会碰到)所以忘记即可。

JavaEE由4部分组成:

  • JAX-WS (Java API for XML-Based Web Services),
  • JAXB (Java Architecture for XML Binding)
  • JAF (the JavaBeans Activation Framework)
  • Common Annotations.

看起来比较多,但是这个特性和JavaSE关系不大。并且Maven也提供了JavaEE(mvnrepository.com/artifact/ja… 第三方引用,所以移除的影响并不会很大。

至于CORBA,使用CORBA开发程序没有太大的兴趣。

CORBA的XML相关模块被移除:java.xml.ws,java.xml.bindjava.xml.wsjava.xml.ws.annotationjdk.xml.bindjdk.xml.ws最终保留:java.xmljava.xml.cryptojdk.xml.dom 模块。

除此之外,还有:

java.se.eejava.activation java.transaction

被移除,但是java11新增一个java.transaction.xa模块

JEP 335: Deprecate the Nashorn JavaScript Engine(弃用 Nashorn JavaScript 引擎)

废除Nashorn javascript引擎,在后续版本准备移除掉,有需要的可以考虑使用GraalVM

最终:Nashorn JavaScript Engine 在 Java 15 已经不可用了。几乎不会用到的东西,为了让读者略微了解一下,这里用JDK8的Nashorn简单做个演示。


public class NashornTest {  
    public static void main(String[] args) throws ScriptException, FileNotFoundException {  
        // 直接使用  
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");  
        engine.eval("print('Hello World!');");  
        // 文件中运行  
        ScriptEngine engine2 = new ScriptEngineManager().getEngineByName("nashorn");  
        engine2.eval(new FileReader("E:\\adongstack\\project\\selfUp\\interview\\src\\main\\resources\\static\\js\\hello.js"));  
    }  
}

现代项目大多数都是前后端分离的,哪怕没有前后端分离也基本没有人会在后端写很多隐患的JS脚本,个人认为是这些原因导致的Nashorn javascript默默无闻的诞生和默默无闻的毁灭。

Nashorn javascript引擎更多了解可以看这篇:mouse0w0.github.io/2018/12/02/…

JEP : 336 : Deprecate the Pack200 Tools and API 废弃Pack200和相关API

Pack200 是自Java5出现的 压缩工具,这个工具能对普通的jar文件进行高效压缩。

Pack200 合并原理是根据类的结构设计以及合并常量池的方式,最后去掉无用信息来实现高效压缩,注意这种压缩只能针对Java文件进行压缩,这种压缩方式是很有意义的,能达到jar包的10% - 40%的压缩率。

Java5中还提供了这一技术的API接口,可以将其嵌入到应用程序中使用。使用的方法很简单,下面的短短几行代码即可以实现jar的压缩和解压:

压缩


Packer packer=Pack200.newPacker();
OutputStream output=new BufferedOutputStream(new FileOutputStream(outfile));
packer.pack(new JarFile(jarFile), output);
output.close();

解压


Unpacker unpacker=Pack200.newUnpacker();
output=new JarOutputStream(new FileOutputStream(jarFile));
unpacker.unpack(pack200File, output);
output.close();

看了上面的介绍,读者可能会跟我有一样的疑问,感觉这不是挺好的一东西么为啥要抛弃? 还能结合SpringBoot的jar包进行压缩,为了进一步了解JDK为啥要在Java11移除,这里找到的JDK官方的说明:

JEP 367: Remove the Pack200 Tools and API (openjdk.org)

我把JDK官方的说明部分拿过来了:


There are three reasons to remove Pack200:
1.  Historically, slow downloads of the JDK over 56k modems were an impediment to Java adoption. The relentless growth in JDK functionality caused the download size to swell, further impeding adoption. Compressing the JDK with Pack200 was a way to mitigate the problem. However, time has moved on: download speeds have improved, and JDK 9 introduced new compression schemes for both the Java runtime ([JEP 220](http://openjdk.java.net/jeps/220)) and the modules used to build the runtime ([JMOD](http://openjdk.java.net/jeps/261#Packaging:-JMOD-files)). Consequently, JDK 9 and later do not rely on Pack200; JDK 8 was the last release compressed with `pack200` at build time and uncompressed with `unpack200` at install time. In summary, a major consumer of Pack200 -- the JDK itself -- no longer needs it.
2.  Beyond the JDK, it was attractive to compress client applications, and especially applets, with Pack200. Some deployment technologies, such as Oracle's browser plug-in, would uncompress applet JARs automatically. However, the landscape for client applications has changed, and most browsers have dropped support for plug-ins. Consequently, a major class of consumers of Pack200 -- applets running in browsers -- are no longer a driver for including Pack200 in the JDK.
3.  Pack200 is a complex and elaborate technology. Its [file format](https://docs.oracle.com/en/java/javase/13/docs/specs/pack-spec.html) is tightly coupled to the [class file format](https://docs.oracle.com/javase/specs/jvms/se13/html/jvms-4.html#jvms-4.1) and the [JAR file format](https://docs.oracle.com/en/java/javase/13/docs/specs/jar/jar.html), both of which have evolved in ways unforeseen by JSR 200. (For example, [JEP 309](http://openjdk.java.net/jeps/309) added a new kind of constant pool entry to the class file format, and [JEP 238](http://openjdk.java.net/jeps/238) added versioning metadata to the JAR file format.) The implementation in the JDK is split between Java and native code, which makes it hard to maintain. The API in `java.util.jar.Pack200` was detrimental to the modularization of the Java SE Platform, leading to [the removal of four of its methods in Java SE 9](http://cr.openjdk.java.net/~iris/se/9/java-se-9-fr-spec/#APIs-removed). Overall, the cost of maintaining Pack200 is significant, and outweighs the benefit of including it in Java SE and the JDK.

一大坨论文一样的英文,这里抽取部分语句的关键点简单概括一下:

第一点:

  • 说白了就是有新人干活还有力气,老东西赶紧退休。过去网络带宽资源吃紧加上网速很慢,超过56k的jar包要下半天,压缩处理能在一定程度上治标。但是现代的网络环境下个几十K的东西几乎都是瞬间完成了,然后JDK9还引入了新的压缩方式(关键)。

第二点:

  • Pack200最后一点夹缝:作为浏览器开发插件压缩的生存空间也没了,现代浏览器插件有独有的开发方式,Java在这一块掀不起什么浪花。

第三点:

  • Pack200 is a complex and elaborate technology。“Pack200是一项复杂而精细的技术”,我愿称之为高情商发言,低情商就是臃肿又难用。后面引经据典“批判了一番多么“精细”和“复杂”导致的问题。

总结:网络下载速度的提升以及java9引入模块化系统之后不再依赖Pack200,因此这个版本将其移除掉。

JDK11 其他内容

Optional 增强

新增了empty()方法来判断指定的 Optional 对象是否为空。


var op = Optional.empty();
System.out.println(op.isEmpty());//判断指定的 Optional 对象是否为空

String 增强


//判断字符串是否为空  
" ".isBlank();//true  
//去除字符串首尾空格  
" Java ".strip();// "Java"  
//去除字符串首部空格  
" Java ".stripLeading();   // "Java "  
//去除字符串尾部空格  
" Java ".stripTrailing();  // " Java"  
//重复字符串多少次  
"Java".repeat(3);             // "JavaJavaJava"  
//返回由行终止符分隔的字符串集合。  
"A\nB\nC".lines().count();    // 3  
"A\nB\nC".lines().collect(Collectors.toList());

移除项

  • 移除了com.sun.awt.AWTUtilities
  • 移除了sun.misc.Unsafe.defineClass
  • 使用java.lang.invoke.MethodHandles.Lookup.defineClass来替代
  • 移除了Thread.destroy()以及 Thread.stop(Throwable)方法
  • 移除了sun.nio.ch.disableSystemWideOverlappingFileLockChecksun.locale.formatasdefault属性
  • 移除了jdk.snmp模块
  • 移除了 javafx ,openjdk 估计是从java10版本就移除了,但是oracle jdk10还尚未移除javafx,而java11版本则oracle的jdk版本也移除了javafx。
  • 移除了Java Mission Control,从JDK中移除之后,需要自己单独下载

移除了这些Root Certificates

  • Baltimore Cybertrust Code Signing CA
  • SECOM
  • AOL and Swisscom

废弃项

  • -XX+AggressiveOpts选项
  • -XX:+UnlockCommercialFeatures
  • -XX:+LogCommercialFeatures选项也不再需要

G1 垃圾收集器升级

JDK11的G1垃圾收集齐相比于 JDK 8可以享受到:

  • 并行的 Full GC
  • 快速的 CardTable 扫描
  • 自适应的堆占用比例调整(IHOP)
  • 在并发标记阶段的类型卸载 ....等等

这些都是针对 G1 的不断增强,串行 Full GC被常常诟病最终Oracle忍无可忍复用了CMS的并行GC代码给实现了(配置参数可以看出端倪,很多和CMS配置仅仅是换了个名字),最终是减少程序员的调优成本和调优时间。

附录

17 个 JEP(JDK Enhancement Proposals,JDK 增强提案)

image.png

下面包含了这17个JEP的提案:

JEP 181: Nest-Based Access Control

JEP 309: Dynamic Class-File Constants

JEP 315: Improve Aarch64 Intrinsics

JEP 318: Epsilon: A No-Op Garbage Collector

JEP 320: Remove the Java EE and CORBA Modules

JEP 321: HTTP Client (Standard)

JEP 323: Local-Variable Syntax for Lambda Parameters

JEP 324: Key Agreement with Curve25519 and Curve448

JEP 327: Unicode 10

JEP 328: Flight Recorder

JEP 329: ChaCha20 and Poly1305 Cryptographic Algorithms

JEP 330: Launch Single-File Source-Code Programs

JEP 331: Low-Overhead Heap Profiling

JEP 332: Transport Layer Security (TLS) 1.3

JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental)

JEP 335: Deprecate the Nashorn JavaScript Engine

JEP 336: Deprecate the Pack200 Tools and API

Jshell(JDK9)

和Go和Python这种自带Shell的语言进行对比,Java的Shell直到Java9才出现,Java9引入了jshell这个交互性工具,让Java也可以像脚本语言一样来运行,可以从控制台启动 jshell。

一个编程语言本该有的东西,这里就不多介绍了。Jshell比较适合刚刚入门Java语言的学习者使用。

写在最后

这篇文章把大部分JDK11新特性简单分析了一遍,如果有错误的论述欢迎指正。JDK的新版本特性还是非常有意思的,虽然不一定能在工作中用上,但是了解这些特性跟进时代是作为Java开发者的基本操守。

相关文章
|
17天前
|
Java 调度
Java中常见锁的分类及概念分析
Java中常见锁的分类及概念分析
16 0
|
17天前
|
Java
Java中ReentrantLock中tryLock()方法加锁分析
Java中ReentrantLock中tryLock()方法加锁分析
13 0
|
2天前
|
安全 Java 大数据
探索Java的奇妙世界:语言特性与实际应用
探索Java的奇妙世界:语言特性与实际应用
|
3天前
|
Java
【Java基础】详解面向对象特性(诸如继承、重载、重写等等)
【Java基础】详解面向对象特性(诸如继承、重载、重写等等)
8 0
|
9天前
|
机器学习/深度学习 Java API
Java8中的新特性
Java8中的新特性
|
10天前
|
Oracle Java 关系型数据库
Java 开发者必备:JDK 版本详解与选择策略(含安装与验证)
Oracle Java SE 支持路线图显示,JDK 8(LTS)支持至2030年,非LTS版本如9-11每6个月发布且支持有限。JDK 11(LTS)支持至2032年,而JDK 17及以上版本现在提供免费商用许可。LTS版本提供长达8年的支持,每2年发布一次。Oracle JDK与OpenJDK有多个社区和公司构建版本,如Adoptium、Amazon Corretto和Azul Zulu,它们在许可证、商业支持和更新方面有所不同。个人选择JDK时,可考虑稳定性、LTS、第三方兼容性和提供商支持。
24 0
|
11天前
|
分布式计算 Java API
Java 8新特性之Lambda表达式与Stream API
【4月更文挑战第16天】本文将介绍Java 8中的两个重要新特性:Lambda表达式和Stream API。Lambda表达式是Java 8中引入的一种新的编程语法,它允许我们将函数作为参数传递给其他方法,从而使代码更加简洁、易读。Stream API是Java 8中引入的一种新的数据处理方式,它允许我们以声明式的方式处理数据,从而使代码更加简洁、高效。本文将通过实例代码详细讲解这两个新特性的使用方法和优势。
|
17天前
|
Java
Java中关于ConditionObject的signal()方法的分析
Java中关于ConditionObject的signal()方法的分析
22 4
|
17天前
|
Java
Java中关于ConditionObject的分析
Java中关于ConditionObject的分析
18 3
|
18天前
|
Java API 开发者
Java 8新特性之函数式编程实战
【4月更文挑战第9天】本文将深入探讨Java 8的新特性之一——函数式编程,通过实例演示如何运用Lambda表达式、Stream API等技术,提高代码的简洁性和执行效率。