从2017年开始,Java版本更新策略从原来的每两年一个新版本,改为每六个月一个新版本,以快速验证新特性,推动Java的发展。从《JVMEcosystemReport2021》中可以看出,目前开发环境中有近半的环境使用Java8,有近半的人转移到了Java11,随着Java17的发布,相信比例会有所变化。 因此,准备出一个系列,配合示例讲解,阐述各个版本的新特性。概述 相较于Java8,Java9没有新增语法糖,但是其增加的特性也都是非常实用的,比如Jigsaw模块化、JShell、发布订阅框架、GC等。本文将快速、高层次的介绍一些新特性,完整的特性可以参加openjdk。java。netprojectsjd 这里需要说明一下,由于Java9并不是长期支持版,当前也是从现在看过去,所以笔者偷个懒,文章的示例代码都是在Java11下写的,可能会与Java9中的定义有些出入,不过,这也没啥,毕竟我们真正使用的时候还是优先考虑长期支持版。Jigsaw模块化 模块化是一个比较大的更新,这让以前AllinOne的Java包拆分成几个模块。这种模块化系统提供了类似OSGi框架系统的功能,比如多个模块可以独立开发,按需引用、按需集成,最终组装成一个完整功能。 模块具有依赖的概念,可以导出功能API,可以隐藏实现细节。 还有一个好处是可以实现JVM的按需使用,能够减小Java运行包的体积,让JVM在内存更小的设备上运行。JVM当时的初衷就是做硬件,也算是不忘初心了。 另外,JVM中com。sun。的之类的内部API,做了更强的封闭,不在允许调用,提升了内核安全。 在使用的时候,我们需要在java代码的顶层目录中定义一个moduleinfo。java文件,用于描述模块信息:modulecn。howardliu。java9。modules。car{requirescn。howardliu。java9。modules。exportscn。howardliu。java9。modules。car。}复制代码 上面描述的信息是:模块cn。howardliu。java9。modules。car需要依赖模块cn。howardliu。java9。modules。engines,并导出模块cn。howardliu。java9。modules。car。handling。 更多的信息可以查看OpenJDK的指引openjdk。java。netprojectsjiJigsaw模块的使用,内容会贴到评论区。全新的HTTP客户端 这是一个千呼万唤始出来的功能,终于有官方API可以替换老旧难用的HttpURLConnection。只不过,在Java9中,新版HTTP客户端是放在孵化模块中(具体信息可以查看openjdk。java。netjeps110)。 老版HTTP客户端存在很多问题,大家开发的时候基本上都是使用第三方HTTP库,比如ApacheHttpClient、Netty、Jetty等。 新版HTTP客户端的目标很多,毕竟这么多珠玉在前,如果还是做成一坨,指定是要被笑死的。所以新版HTTP客户端列出了16个目标,包括简单易用、打印关键信息、WebSocket、HTTP2、HTTPSTLS、良好的性能、非阻塞API等等。 我们先简单的瞅瞅:finalStringurlhttps:postmanecho。finalHttpRequestrequestHttpRequest。newBuilder()。uri(newURI(url))。GET()。build();finalHttpResponseStringresponseHttpClient。newHttpClient()。send(request,HttpResponse。BodyHandlers。ofString());finalHttpHeadersheadersresponse。headers();headers。map()。forEach((k,v)System。out。println(k:v));System。out。println(response。statusCode());System。out。println(response。body());复制代码 新版HTTP客户端可以在Java11中正常使用了,上面的代码也是在Java11中写的,API是在java。net。http包中。改进的进程API 在Java9中提供的进程API,可以控制和管理操作系统进程。也就是说,可以在代码中管理当前进程,甚至可以销毁当前进程。进程信息 这个功能是由java。lang。ProcessHandle提供的,我们来瞅瞅怎么用:finalProcessHandleselfProcessHandle。current();finallongpidself。pid();System。out。println(PID:pid);finalProcessHandle。InfoprocInfoself。info();procInfo。arguments()。ifPresent(x{for(Strings:x){System。out。println(s);}});procInfo。commandLine()。ifPresent(System。out::println);procInfo。startInstant()。ifPresent(System。out::println);procInfo。totalCpuDuration()。ifPresent(System。out::println);复制代码 java。lang。ProcessHandle。Info中提供了丰富的进程信息销毁进程 我们还可以使用java。lang。ProcessHandledestroy方法销毁进程,我们演示一下销毁子进程:ProcessHandle。current()。children()。forEach(procHandle{System。out。println(procHandle。pid());System。out。println(procHandle。destroy());});复制代码 从Java8之后,我们会发现Java提供的API使用了Optional、Stream等功能,Eatingyourowndogfood也是比较值得学习的。其他小改动 Java9中还对做了对已有功能做了点改动,我们来瞅瞅都有哪些。改进trywithresources 从Java7开始,我们可以使用trywithresources语法自动关闭资源,所有实现了java。lang。AutoCloseable接口,可以作为资源。但是这里会有一个限制,就是每个资源需要声明一个新变量。 也就是这样:publicstaticvoidtryWithResources()throwsIOException{try(FileInputStreamin2newFileInputStream(。)){dosomething}}复制代码 对于这种直接使用的还算方便,但如果是需要经过一些列方法定义的呢?就得写成下面这个样子:finalReaderinputStringnewStringReader(www。howardliu。cn看山);finalBufferedReaderbrnewBufferedReader(inputString);其他一些逻辑try(BufferedReaderbr1br){System。out。println(br1。lines());}复制代码 在Java9中,如果资源是final定义的或者等同于final变量,就不用声明新的变量名,可以直接在trywithresources中使用:finalReaderinputStringnewStringReader(www。howardliu。cn看山);finalBufferedReaderbrnewBufferedReader(inputString);其他一些逻辑try(br){System。out。println(br。lines());}复制代码改进钻石操作符(DiamondOperator) 钻石操作符(也就是)是Java7引入的,可以简化泛型的书写,比如:MapString,ListStringstrsMapnewTreeMapString,ListString();复制代码 右侧的TreeMap类型可以根据左侧的泛型定义推断出来,借助钻石操作符可以简化为:MapString,ListStringstrsMapnewTreeMap();复制代码 看山会简洁很多,的写法就是钻石操作符(DiamondOperator)。 但是这种写法不适用于匿名内部类。比如有个抽象类:abstractstaticclassConsumerT{privateTpublicConsumer(Tcontent){this。}abstractvoidaccept();publicTgetContent(){}}复制代码 在Java9之前,想要实现匿名内部类,就需要写成:finalConsumerIntegerintConsumernewConsumerInteger(1){Overridevoidaccept(){System。out。println(getContent());}};intConsumer。accept();finalC?extendsNumbernumConsumernewConsumerNumber(BigDecimal。TEN){Overridevoidaccept(){System。out。println(getContent());}};numConsumer。accept();finalC?objConsumernewConsumerObject(看山){Overridevoidaccept(){System。out。println(getContent());}};objConsumer。accept();复制代码 在Java9之后就可以使用钻石操作符了:finalConsumerIntegerintConsumernewConsumer(1){Overridevoidaccept(){System。out。println(getContent());}};intConsumer。accept();finalC?extendsNumbernumConsumernewConsumer(BigDecimal。TEN){Overridevoidaccept(){System。out。println(getContent());}};numConsumer。accept();finalC?objConsumernewConsumer(看山){Overridevoidaccept(){System。out。println(getContent());}};objConsumer。accept();复制代码私有接口方法 如果说钻石操作符是代码的简洁可读,那接口的私有方法就是比较实用的一个扩展了。 在Java8之前,接口只能有常量和抽象方法,想要有具体的实现,就只能借助抽象类,但是Java是单继承,有很多场景会受到限制。 在Java8之后,接口中可以定义默认方法和静态方法,提供了很多扩展。但这些方法都是public方法,是完全对外暴露的。如果有一个方法,只想在接口中使用,不想将其暴露出来,就没有办法了。这个问题在Java9中得到了解决。我们可以使用private修饰,限制其作用域。 比如:publicinterfaceMetric{常量StringNAMEMETRIC;抽象方法voidinfo();私有方法privatevoidappend(Stringtag,Stringinfo){buildMetricInfo();System。out。println(NAME〔tag〕:info);clearMetricInfo();}默认方法defaultvoidappendGlobal(Stringmessage){append(GLOBAL,message);}默认方法defaultvoidappendDetail(Stringmessage){append(DETAIL,message);}私有静态方法privatestaticvoidbuildMetricInfo(){System。out。println(buildbasemetric);}私有静态方法privatestaticvoidclearMetricInfo(){System。out。println(clearbasemetric);}}复制代码JShell JShell就是Java语言提供的REPL(ReadEvalPrintLoop,交互式的编程环境)环境。在Python、Node之类的语言,很早就带有这种环境,可以很方便的执行Java语句,快速验证一些语法、功能等。jshell欢迎使用JShell版本13。0。9要大致了解该版本,请键入:helpintro复制代码 我们可以直接使用help查看命令jshellhelp键入Java语言表达式,语句或声明。或者键入以下命令之一:list〔名称或idallstart〕列出您键入的源edit名称或id。很多的内容,鉴于篇幅,先隐藏复制代码 我们看下一些简单的操作:jshellThisisatest。。substring(5,10);2isajshell3134复制代码 也可以创建方法:jshellintmulitiTen(inti){returni10;}已创建方法mulitiTen(int)jshellmulitiTen(3)630复制代码 想要退出JShell直接输入:jshellexit再见复制代码JCMD新增子命令 jcmd是用于向本地jvm进程发送诊断命令,这个命令是从JDK7提供的命令行工具,常用于快速定位线上环境故障。 在JDK9之后,提供了一些新的子命令,查看JVM中加载的所有类及其继承结构的列表。比如:jcmd22922VM。classhierarchyisjava。net。Socket22922:java。lang。Objectnulljava。net。Socketnullimplementsjava。io。Closeablenull(declaredintf)implementsjava。lang。AutoCloseablenull(inheritedintf)sun。nio。ch。SocketAdaptornullimplementsjava。lang。AutoCloseablenull(inheritedintf)implementsjava。io。Closeablenull(inheritedintf)复制代码 第一个参数是进程ID,都是针对这个进程执行诊断。我们还可以使用setvmflag参数在线修改JVM参数,这种操作无需重启JVM进程。 有时候还需要查看当前进程的虚拟机参数选项和当前值:jcmd22922VM。flagsall。多分辨率图像API 在Java9中定义了多分辨率图像API,我们可以很容易的操作和展示不同分辨率的图像了。java。awt。image。MultiResolutionImage将一组具有不同分辨率的图像封装到单个对象中。java。awt。Graphics类根据当前显示DPI度量和任何应用的转换从多分辨率图像中获取变量。 以下是多分辨率图像的主要操作方法:ImagegetResolutionVariant(doubledestImageWidth,doubledestImageHeight):获取特定分辨率的图像变体表示一张已知分辨率单位为DPI的特定尺寸大小的逻辑图像,并且这张图像是最佳的变体。ListgetResolutionVariants():返回可读的分辨率的图像变体列表。 我们来看下应用:finalListImageimagesList。of(ImageIO。read(newURL(https:static。howardliu。cnaboutkanshanshuo2。png)),ImageIO。read(newURL(https:static。howardliu。cnabouthellokanshan。png)),ImageIO。read(newURL(https:static。howardliu。cnaboutevil20coder。jpg)));读取所有图片finalMultiResolutionImagemultiResolutionImagenewBaseMultiResolutionImage(images。toArray(newImage〔0〕));获取图片的所有分辨率finalListImagevariantsmultiResolutionImage。getResolutionVariants();System。out。println(Totalnumberofimages:variants。size());for(Imageimg:variants){System。out。println(img);}根据不同尺寸获取对应的图像分辨率Imagevariant1multiResolutionImage。getResolutionVariant(100,100);System。out。printf(Imagefordestination〔d,d〕:〔d,d〕,100,100,variant1。getWidth(null),variant1。getHeight(null));Imagevariant2multiResolutionImage。getResolutionVariant(200,200);System。out。printf(Imagefordestination〔d,d〕:〔d,d〕,200,200,variant2。getWidth(null),variant2。getHeight(null));Imagevariant3multiResolutionImage。getResolutionVariant(300,300);System。out。printf(Imagefordestination〔d,d〕:〔d,d〕,300,300,variant3。getWidth(null),variant3。getHeight(null));Imagevariant4multiResolutionImage。getResolutionVariant(400,400);System。out。printf(Imagefordestination〔d,d〕:〔d,d〕,400,400,variant4。getWidth(null),variant4。getHeight(null));Imagevariant5multiResolutionImage。getResolutionVariant(500,500);System。out。printf(Imagefordestination〔d,d〕:〔d,d〕,500,500,variant5。getWidth(null),variant5。getHeight(null));复制代码变量句柄(VariableHandles) 变量句柄(VariableHandles)的API主要是用来替代java。util。concurrent。atomic包和sun。misc。Unsafe类的部分功能,并且提供了一系列标准的内存屏障操作,用来更加细粒度的控制内存排序。一个变量句柄是一个变量(任何字段、数组元素、静态表里等)的类型引用,支持在不同访问模型下对这些类型变量的访问,包括简单的readwrite访问,volatile类型的readwrite访问,和CAS(compareandswap)等。 这部分内容涉及反射、内联、并发等内容,后续会单独介绍,文章最终会发布在从小工到专家的Java进阶之旅中,敬请关注。发布订阅框架 在Java9中增加的java。util。concurrent。Flow支持响应式API的发布订阅框架,他们提供在JVM上运行的许多异步系统之间的互操作性。我们可以借助SubmissionPublisher定制组件。 关于响应式API的内容可以先查看www。reactivestreams。org的内容,后续单独介绍,从小工到专家的Java进阶之旅中,敬请关注。怎么感觉给自己刨了这么多坑,得抓紧时间填坑了。统一JVM日志记录 在这个版本中,为JVM的所有组件引入了一个通用的日志系统。它提供了日志记录的基础。这个功能是通过Xlog启动参数指定,并且定义很多标签用来定义不同类型日志,比如:gc(垃圾收集)、compiler(编译)、threads(线程)等等。比如,我们定义debug等级的gc日志,日志存储在gc。log文件中:javaXlog:gcdebug:filegc。log:none复制代码 因为参数比较多,我们可以通过javaXlog:help查看具体定义参数。而且日志配置可以通过jcmd命令动态修改,比如,我们将日志输出文件修改为gcother。log:jcmd{PID}VM。logoutputgcother。logwhatgc复制代码新的API不可变集合 在Java9中增加的java。util。List。of()、java。util。Set。of()、java。util。Map。of()系列方法,可以一行代码创建不可变集合。在Java9之前,我们想要初始化一个有指定值的集合,需要执行一堆add或put方法,或者依赖guava框架。 而且,这些集合对象是可变的,假设我们将值传入某个方法,我们就没有办法控制这些集合的值不会被修改。在Java9之后,我们可以借助ImmutableCollections中的定义实现初始化一个不可变的、有初始值的集合了。如果对这些对象进行修改(新增元素、删除元素),就会抛出UnsupportedOperationException异常。 这里不得不提的是,Java开发者们也是考虑了性能,针对不同数量的集合,提供了不同的实现类:List12、Set12、Map1专门用于少量(List和Set是2个,对于Map是1对)元素数量的场景ListN、SetN、MapN用于数据量多(List和Set是超过2个,对于Map是多余1对)的场景改进的Optional类 Java9中为Optional添加了三个实用方法:stream、ifPresentOrElse、or。 stream是将Optional转为一个Stream,如果该Optional中包含值,那么就返回包含这个值的Stream,否则返回Stream。empty()。比如,我们有一个集合,需要过滤非空数据,在Java9之前,写法如下:finalListOptionalStringlistArrays。asList(Optional。empty(),Optional。of(看山),Optional。empty(),Optional。of(看山的小屋));finalListStringfilteredListlist。stream()。flatMap(oo。isPresent()?Stream。of(o。get()):Stream。empty())。collect(Collectors。toList());复制代码 在Java9之后,我们可以借助stream方法:finalListStringfilteredListJava9list。stream()。flatMap(Optional::stream)。collect(Collectors。toList());复制代码 ifPresentOrElse:如果一个Optional包含值,则对其包含的值调用函数action,即action。accept(value),这与ifPresent方法一致;如果Optional不包含值,那会调用emptyAction,即emptyAction。run()。效果如下:OptionalIntegeroptionalOptional。of(1);optional。ifPresentOrElse(xSystem。out。println(Value:x),()System。out。println(NotPresent。));optionalOptional。empty();optional。ifPresentOrElse(xSystem。out。println(Value:x),()System。out。println(NotPresent。));输出结果为:作者:看山佚名复制代码 or:如果值存在,返回Optional指定的值,否则返回一个预设的值。效果如下:OptionalStringoptional1Optional。of(看山);SupplierOptionalStringsupplierString()Optional。of(佚名);optional1optional1。or(supplierString);optional1。ifPresent(xSystem。out。println(作者:x));optional1Optional。empty();optional1optional1。or(supplierString);optional1。ifPresent(xSystem。out。println(作者:x));输出结果为:作者:看山作者:佚名复制代码文末总结 本文介绍了Java9新增的特性,完整的特性清单可以从openjdk。java。netprojectsjdJava8到Java17的新特性系列完成后补充,青山不改,绿水长流,我们下次见。 原文链接:https:juejin。cnpost7061389685699903525