Nuevas funciones de JDK: explicación detallada de las nuevas funciones de JDK 9

Descripción general de Java 9

¿Cuáles son las nuevas características de Java 9?

Java 9 proporciona más de 150 características nuevas, incluido el muy esperado sistema modular, herramientas REPL interactivas: JShell, herramientas de compilación JDK, API pública de Java y código privado, así como mejoras de seguridad, mejoras de extensión, mejoras de gestión del rendimiento, etc. Se puede decir que Java 9 es un proyecto de sistema enorme y ha realizado un cambio general por completo.

Específicamente:

  • Sistema modular (núcleo)
  • Comandos JShell (núcleo)
  • Paquete jar compatible con múltiples versiones
  • métodos privados de interfaz
  • Actualización de uso del operador Diamond
  • Mejoras de sintaxis: declaración de prueba
  • Subrayar restricciones de uso
  • Cambios en la estructura de almacenamiento de cadenas
  • Propiedades de colección convenientes: of()
  • API de transmisión mejorada
  • API de imágenes de resolución múltiple
  • Nueva API de cliente HTTP
  • API relacionadas obsoletas
  • Herramientas inteligentes de compilación de Java
  • Sistema de registro JVM unificado
  • Soporte HTML 5 para javadoc
  • Actualización del motor Javascript: Nashorn
  • compilador dinámico para java

Cambios en versiones posteriores del lenguaje java.

A partir de Java 9, el ciclo de lanzamiento planificado de Java es de seis meses. La próxima versión principal de Java se lanzará en marzo de 2018, denominada Java 18.3, seguida de Java 18.9 seis meses después.

Esto significa que las actualizaciones de Java han cambiado del ciclo de lanzamiento tradicional basado en funciones a un modelo de lanzamiento basado en tiempo (ciclo de 6 meses), y las funciones comerciales originales de Oracle JDK serán gradualmente de código abierto.

En respuesta a las necesidades de los clientes empresariales, Oracle lanzará versiones de soporte a largo plazo (soporte a largo plazo) en un ciclo de tres años.

Qué pensar sobre las actualizaciones de Java 9

El ciclo de lanzamiento más rápido de Java significa que los desarrolladores no tendrán que esperar tanto como antes para obtener lanzamientos importantes. Esto también significa que los desarrolladores probablemente se salten Java 9 y sus características modulares inmaduras, y sólo tendrán que esperar otros 6 meses para introducir una nueva versión, que puede resolver los enredos de los desarrolladores.

Filosofía de Oracle: ejecutar rápido en pequeños pasos e iterar rápidamente

Recursos de Java 9

Descarga de jdk 9:http://www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.html

Descargue el documento de ayuda de Java 9:http://www.oracle.com/technetwork/java/javase/documentation/jdk9-doc-downloads-3850606.html

Documentación en línea de Oracle JDK 9:https://docs.oracle.com/javase/9/

Documentación de nuevas funciones para Java 9:http://openjdk.java.net/projects/jdk9/

下面表格是 Java 9 的新特性,左侧是英文,右侧是中文,左侧点击即可跳转到官网对该新特性的介绍文档。

| Features | 特性 | | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | | JEP 102:Process API Updates(opens new window) | 102:API 更新过程 | | JEP 110:HTTP 2 Client(opens new window) | 110:2 HTTP 客户端 | | JEP 143:Improve Contended Locking(opens new window) | 143:改善竞争锁 | | JEP 158:Unified JVM Logging(opens new window) | 158:统一 JVM 日志记录 | | JEP 165:Compiler Control(opens new window) | 165:编译器控制 | | JEP 193:Variable Handles(opens new window) | 193:变量处理 | | JEP 197:Segmented Code Cache(opens new window) | 197:分段代码缓存 | | JEP 199:Smart Java Compilation, Phase Two(opens new window) | 199:聪明的 Java 编译,第二阶段 | | JEP 200:The Modular JDK(opens new window) | 200:模块化 JDK | | JEP 201:Modular Source Code(opens new window) | 201:模块化的源代码 | | JEP 211:Elide Deprecation Warnings on Import Statements(opens new window) | 211:导入语句时省略警告信息 | | JEP 212:Resolve Lint and Doclint Warnings(opens new window) | 212:解决 Lint 和 Doclint 警告 | | JEP 213:Milling Project Coin(opens new window) | 213:研磨项目 Coin | | JEP 214:Remove GC Combinations Deprecated in JDK 8(opens new window) | 214:移除 JDK 8 过时的 GC 组合 | | JEP 215:Tiered Attribution for javac(opens new window) | 215:javac 分层归因 | | JEP 216:Process Import Statements Correctly(opens new window) | 216:正确处理导入语句 | | JEP 217:Annotations Pipeline 2.0(opens new window) | 217:2.0 注释管道 | | JEP 219:Datagram Transport Layer Security (DTLS)(opens new window) | 219:数据报传输层安全性(DTLS) | | JEP 220:Modular Run-Time Images(opens new window) | 220:模块化运行时图像 | | JEP 221:Simplified Doclet API(opens new window) | 221:简化 Doclet API | | JEP 222:jshell: The Java Shell (Read-Eval-Print Loop)(opens new window) | 222:jshell:Java Shell(Read-Eval-Print-Loop) | | JEP 223:New Version-String Scheme(opens new window) | 223:字符串新版本方案 | | JEP 224:HTML5 Javadoc(opens new window) | 224:Javadoc 支持 HTML5 | | JEP 225:Javadoc Search(opens new window) | 225:Javadoc 搜索 | | JEP 226:UTF-8 Property Files(opens new window) | 226:utf - 8 属性文件 | | JEP 227:Unicode 7.0(opens new window) | 227:Unicode 7.0 | | JEP 228:Add More Diagnostic Commands(opens new window) | 228:添加更多的诊断命令 | | JEP 229:Create PKCS12 Keystores by Default(opens new window) | 229:创建 PKCS12 默认密钥存储库 | | JEP 231:Remove Launch-Time JRE Version Selection(opens new window) | 231:删除启动 JRE 版本选择 | | JEP 232:Improve Secure Application Performance(opens new window) | 232:提高安全应用程序性能 | | JEP 233:Generate Run-Time Compiler Tests Automatically(opens new window) | 233:自动生成运行时编译器测试 | | JEP 235:Test Class-File Attributes Generated by javac(opens new window) | 235:测试 javac 生成的类文件属性 | | JEP 236:Parser API for Nashorn(opens new window) | 236:Nashorn 解析器 API | | JEP 237:Linux/AArch64 Port(opens new window) | 237:Linux / AArch64 端口 | | JEP 238:Multi-Release JAR Files(opens new window) | 238:Multi-Release JAR 文件 | | JEP 240:Remove the JVM TI hprof Agent(opens new window) | 240:删除 JVM TI hprof 代理 | | JEP 241:Remove the jhat Tool(opens new window) | 241:删除 jhat 工具 | | JEP 243:Java-Level JVM Compiler Interface(opens new window) | 243:java 级别 JVM 编译器接口 | | JEP 244:TLS Application-Layer Protocol Negotiation Extension(opens new window) | 244:TLS 应用层协议谈判扩展 | | JEP 245:Validate JVM Command-Line Flag Arguments(opens new window) | 245:JVM 命令行标记参数进行验证 | | JEP 246:Leverage CPU Instructions for GHASH and RSA(opens new window) | 246:利用 CPU 指令 GHASH 和 RSA | | JEP 247:Compile for Older Platform Versions(opens new window) | 247:老平台版本编译 | | JEP 248:Make G1 the Default Garbage Collector(opens new window) | 248:设置 G1 为默认的垃圾收集器 | | JEP 249:OCSP Stapling for TLS(opens new window) | 249:OCSP 装订 TLS | | JEP 250:Store Interned Strings in CDS Archives(opens new window) | 250:CDS 档案中存储实际字符串 | | JEP 251:Multi-Resolution Images(opens new window) | 251:多分辨率图像 | | JEP 252:Use CLDR Locale Data by Default(opens new window) | 252:使用系统默认语言环境数据 | | JEP 253:Prepare JavaFX UI Controls & CSS APIs for Modularization(opens new window) | 253:准备 JavaFX UI 控件和 CSS api 用于模块化 | | JEP 254:Compact Strings(opens new window) | 254:紧凑的字符串 | | JEP 255:Merge Selected Xerces 2.11.0 Updates into JAXP(opens new window) | 255:合并选定的 Xerces 2.11.0 更新到 JAXP | | JEP 256:BeanInfo Annotations(opens new window) | 256:BeanInfo 注释 | | JEP 257:Update JavaFX/Media to Newer Version of GStreamer(opens new window) | 257:更新 JavaFX /Media 到 GStreamer 的新版本 | | JEP 258:HarfBuzz Font-Layout Engine(opens new window) | 258:HarfBuzz 文字编排引擎 | | JEP 259:Stack-Walking API(opens new window) | 259:提供 Stack – Walking API | | JEP 260:Encapsulate Most Internal APIs(opens new window) | 260:封装内部 api | | JEP 261:Module System(opens new window) | 261:模块系统 | | JEP 262:TIFF Image I/O(opens new window) | 262:TIFF 图像 I/O | | JEP 263:HiDPI Graphics on Windows and Linux(opens new window) | 263:Windows 和 Linux 上的 HiDPI 图形 | | JEP 264:Platform Logging API and Service(opens new window) | 264:日志 API 和服务平台 | | JEP 265:Marlin Graphics Renderer(opens new window) | 265:Marlin 图形渲染器 | | JEP 266:More Concurrency Updates(opens new window) | 266:更多的并发更新 | | JEP 267:Unicode 8.0(opens new window) | 267:Unicode 8.0 | | JEP 268: XML Catalogs(opens new window) | 268:XML 目录 | | JEP 269:Convenience Factory Methods for Collections(opens new window) | 269:方便的集合工厂方法 | | JEP 270:Reserved Stack Areas for Critical Sections(opens new window) | 270:保留堆栈领域至关重要的部分 | | JEP 271:Unified GC Logging(opens new window) | 271:统一的 GC 日志记录 | | JEP 272:Platform-Specific Desktop Features(opens new window) | 272:特定于平台的桌面功能 | | JEP 273:DRBG-Based SecureRandom Implementations(opens new window) | 273:基于 DRBG 的 SecureRandom 实现 | | JEP 274:Enhanced Method Handles(opens new window) | 274:增强的方法处理 | | JEP 275:Modular Java Application Packaging(opens new window) | 275:模块化 Java 应用程序包装 | | JEP 276:Dynamic Linking of Language-Defined Object Models(opens new window) | 276:语言定义对象模型的动态链接 | | JEP 277:Enhanced Deprecation(opens new window) | 277:增强的弃用 | | JEP 278:Additional Tests for Humongous Objects in G1(opens new window) | 278:为 G1 的极大对象提供额外的测试 | | JEP 279:Improve Test-Failure Troubleshooting(opens new window) | 279:改善测试失败的故障排除 | | JEP 280:Indify String Concatenation(opens new window) | 280:Indify 字符串连接 | | JEP 281:HotSpot C++ Unit-Test Framework(opens new window) | 281:热点 c++ 的单元测试框架 | | JEP 282:jlink:The Java Linker(opens new window) | 282:jlink:Java 连接器 | | JEP 283:Enable GTK 3 on Linux(opens new window) | 283:在 Linux 上启用 GTK 3 | | JEP 284:New HotSpot Build System(opens new window) | 284:新热点的构建系统 | | JEP 285:Spin-Wait Hints(opens new window) | 285:循环等待提示 | | JEP 287:SHA-3 Hash Algorithms(opens new window) | 287:SHA-3 散列算法 | | JEP 288:Disable SHA-1 Certificates(opens new window) | 288:禁用 sha - 1 证书 | | JEP 289:Deprecate the Applet API(opens new window) | 289:标记过时的 Applet API | | JEP 290:Filter Incoming Serialization Data(opens new window) | 290:过滤传入的序列化数据 | | JEP 291:Deprecate the Concurrent Mark Sweep (CMS) Garbage Collector(opens new window) | 291:反对并发标记清理垃圾收集器(CMS) | | JEP 292:Implement Selected ECMAScript 6 Features in Nashorn(opens new window) | 292:实现选定的 ECMAScript Nashorn 6 特性 | | JEP 294:Linux/s390x Port(opens new window) | 294:Linux / s390x 端口 | | JEP 295:Ahead-of-Time Compilation(opens new window) | 295:提前编译 | | JEP 297:Unified arm32/arm64 Port(opens new window) | 297:统一的 arm32 / arm64 端口 | | JEP 298:Remove Demos and Samples(opens new window) | 298:删除演示和样本 | | JEP 299:Reorganize Documentation(opens new window) | 299:整理文档 |

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

JSR(Java Specification Requests):Java 规范提案,新特性的规范出现在这一阶段,是指向 JCP(Java Community Process)提出新增一个 标准化技术规范的正式请求。请求可以来自于小组/项目、JEP、JCP 成员或者 Java 社区(community)成员的提案,每个 Java 版本都由相应的 JSR 支持。

  • 小组:对特定技术内容,比如安全、网络、HotSpot 等有共同兴趣的组织和个人
  • 项目:编写代码、文档以及其他工作,至少由一个小组赞助和支持,比如最近的 Lambda 计划,JigSaw 计划等

JDK和JRE的改变

JDK与JRE的关系

JDK:Java Development Kit(Java 开发工具包)

JRE:Java Runtime Environment(Java 运行环境)

image-20220210151110397

说明:

  • JDK = JRE + 开发工具集(例如 Javac 编译工具等)
  • JRE = JVM + Java SE 标准类库

JDK8的目录结构

image-20220210151235086

说明:

| 目录 | 作用 | | ---------- | --------------------------------------------------------------------------------- | | bin 目录 | 包含命令行开发和调试工具,如 javac,jar 和 javadoc | | include 目录 | 包含在编译本地代码时使用的 C/C++ 头文件 | | lib 目录 | 包含 JDK 工具的几个 JAR 和其他类型的文件。 它有一个 tools.jar 文件,其中包含 javac 编译器的 Java 类 | | jre/bin 目录 | 包含基本命令,如 Java 命令。 在 Windows 平台上,它包含系统的运行时动态链接库(DLL) | | jre/lib 目录 | 包含用户可编辑的配置文件,如 .properties.policy 文件。包含几个 JAR。rt.jar文件包含运行时的 Java 类和资源文件 |

JDK9的目录结构

image-20220210151408535

说明:

| 目录 | 作用 | | ---------- | ------------------------------------------------------------ | | bin 目录 | 包含所有命令。 在 Windows 平台上,它继续包含系统的运行时动态链接库 | | conf 目录 | 包含用户可编辑的配置文件,例如以前位于 jre\lib 目录中的 .properties.policy文件 | | include 目录 | 包含要在以前编译本地代码时使用的 C/C++ 头文件。它只存在于 JDK 中 | | jmods 目录 | 包含 JMOD 格式的平台模块。创建自定义运行时映像时需要它。它只存在于 JDK 中 | | legal 目录 | 包含法律声明 | | lib 目录 | 包含非 Windows 平台上的动态链接本地库。其子目录和文件不应由开发人员直接编辑或使用 |

模块化系统

参考 Jigsaw,现在改名为 Modularity。

官方 Feature:

产生背景及意义

谈到 Java 9 大家往往第一个想到的就是 Jigsaw 项目。众所周知,Java 已经发展超过 20 年(95 年最初发布),Java 和相关生态在不断丰富的同时也越来越暴露出一些问题:

  • Java 运行环境的膨胀和臃肿。每次 JVM 启动的时候,至少会有 30~60MB 的内存加载,主要原因是 JVM 需要加载 rt.jar,不管其中的类是否被classloader 加载,第一步 整个 jar 都会被 JVM 加载到内存当中去(而模块化可以根据模块的需要加载程序运行需要的 Class)
  • 当代码库越来越大,创建复杂,盘根错节的「意大利面条式代码」的几率呈指数级的增长。不同版本的 类库交叉依赖 导致让人头疼的问题,这些都 阻碍了 Java 开发和运行效率的提升
  • 很难真正地对代码进行封装,而系统并没有对不同部分(也就是 JAR 文件)之间的依赖关系有个明确的概念。每一个公共类都可以被类路径之下任何其它的公共类所访问到,这样就会导致无意中使用了并不想被公开访问的 API
  • 类路径本身也存在问题: 你怎么知晓所有需要的 JAR 都已经有了,或者是不是会有重复的项呢

同时,由于 兼容性等各方面的掣肘,对 Java 进行大刀阔斧的革新越来越困难,Jigsaw 从 Java 7 阶段就开始筹备,Java 8 阶段进行了大量工作,终于在 Java 9 里落地,一种千呼万唤始出来的意味。

Jigsaw 项目(后期更名为 Modularity)的工作量和难度大大超出了初始规划。JSR 376 Java 平台模块化系统(JPMS,Java Platform Module System)作为 Jigsaw 项目的核心,其主体部分被分解成 6 个 JEP(JDK Enhancement Proposals)。

作为 Java 9 平台 最大的一个特性,随着 Java 平台模块化系统的落地,开发人员无需再为不断膨胀的 Java 平台苦恼,例如,您可以 使用 jlink 工具,根据需要定制运行时环境。这对于拥有大量镜像的容器应用场景或复杂依赖关系的大型应用等,都具有非常重要的意义。

本质上讲,模块(module)的概念,其实就是 package 外再裹一层,也就是说,用模块来管理各个 package,通过声明某个 package 暴露,不声明默认就是隐藏。因此,模块化使得代码组织上 更安全,因为它可以指定哪些部分可以暴露,哪些部分隐藏。

设计理念

模块独立、化繁为简。

模块化(以 Java 平台模块系统的形式)将 JDK 分成一组模块,可以在编译时,运行时或者构建时进行组合。

实现目标

  • 主要目的在于减少内存的开销
  • 只须必要模块,而非全部 JDK 模块,可 简化各种类库和大型应用的开发和维护
  • 改进 Java SE 平台,使其可以 适应不同大小的计算设备
  • 改进其安全性,可维护性,提高性能

代码举例

模块将由通常的类和新的模块声明文件(module-info.java)组成。该文件是位于 Java 代码结构的顶层,该模块描述符明确地定义了 我们的模块需要什么依赖关系,以及哪些模块被外部使用。在 exports 子句中未提及的所有包默认情况下将封装在模块中,不能在外部使用。

首先结构如下:

根目录 (java9test) ├── java9demo │   ├── src │   │   ├── cn.youngkbt │   │   │ ├── bean │ │ │ │ └── Person.java │   │   │   ├── entity │ │ │ │ └── User.java │   │   │   └── module-info.java ├── javatest │   ├── src │   │   ├── cn.youngkbt │   │   │ ├── java │ │ │ │ └── ModuleTest.java

如图:

image-20220210155128270

  • 创建 java 9demo 模块 src 的 module-info.java 文件

    这个文件导出包名给其他模块使用

    module java9demo {    exports cn.youngkbt.bean;    exports cn.youngkbt.entry; }

    exports:控制着哪些包可以被其它模块访问到。所有不被导出的包默认都被封装在模块里面。

  • 然后创建 javatest 模块 src 的 module-info.java 文件

    这个文件则是引用 java9demo 导出的包和其他需要的包

    module javatest {    requires java9demo;    requires java.logging; }

    requires:指明对其它模块的依赖。

  • java 9demo 模块中的 ModuleTest 类使用如下(如果不 requires 引入,则获取不到包下的类):

    public class ModuleTest { ​    private static final Logger LOGGER = Logger.getLogger("youngkbt"); ​    public static void main(String[] args) { ​        Person p = new Person("Tom",12); ​        System.out.println(p); ​        User user = new User(); ​        LOGGER.info("aaaaaa"); ​   } }

总结

每个模块创建 module-info.java 文件,导出其他模块需要的包,引入其他模块已经导出的包。

其实 Maven 通过 pom.xml 已经实现了模块化,这里演示的是没有使用 Maven 项目的情况下,如何在不同模块下引入其他模块的包。

关于更多 Java 9 模块编程的内容请参考一本书:《Java 9 Modularity (opens new window)》,里面讲的比较详细,介绍了当前 Java 对 jar 之间以来的管理是多么的混乱,引入 modularity 之后的改变会是很明显的差别。

JShell

官方 Feature:222:

产生背景

像 Python 和 Scala 之类的语言早就有交互式编程环境 REPL(read - evaluate - print - loop)了,以交互式的方式对语句和表达式进行求值。开发者只需要输入一些代码,就可以在编译前获得对程序的反馈。而 之前的 Java 版本要想执行代码,必须创建文件、声明类、提供测试方法方可实现

理念设计

即写即得、快速运行。

目标实现

Java 9 中终于拥有了 REPL 工具:JShell。利用 JShell 在没有创建类的情况下直接声明变量,计算表达式,执行语句。即开发时可以在命令行里直接运行 Java 的代码,而无需创建 Java 文件,无需跟人解释 public static void main(String[] args) 这句废话。

JShell 也可以从 文件中加载语句 或者将语句保存到文件中。

JShell 也可以是 Tab 键进行自动补全自动添加分号

举例使用

调出 JShell

如果已经配置了 Java 9 的环境变量,则直接在命令行输入 jshell 即可,如果没有配置环境遍历,则去 Java 根目录下的 bin 目录找到 jshell.exe

image-20220210161720608

获取帮助

任何命令行工具都有 help 帮助提示,JShell 也不例外,输入 /help intro 即可看到帮助。

image-20220210161814112

基本使用

和在 Java 文件写代码一样,JShell 会自动执行输入的代码,并且保存起来,当退出后,再清空代码。

System.out.println("你好!world"); ​ int  i = 10; ​ int j = 20; ​ int k = i + k; ​ System.out.println(k); // 30 ​ public int add(int m, int n){    return m + n; } ​ int k = add(1,2); ​ System.out.println(k); // 3

image-20220210161919681

image-20220210161931145

注意:在 JShell 环境下,语句末尾的 ; 是可选的。但推荐还是最好加上。提高代码可读性。

导入指定的包

指定包路径,可导入包。

image-20220210162155889

默认已经导入如下的所有包:(包含 java.lang 包)

通过 /import 指令可以查看默认导入的包:

image-20220210162234969

只需按下 Tab 键,就能自动补全代码

image-20220210162249456

列出当前 session 里所有有效的代码片段

通过 /list 指令列出曾经输入的代码片段。

image-20220210162410111

查看当前 session 下所有创建过的变量

通过 /var 指令列出所有创建过的变量。

image-20220210162420212

查看当前 session 下所有创建过的方法

通过 /methods 指令列出所有创建过的方法。

image-20220210162441281

注意:我们还可以重新定义相同方法名和参数列表的方法,即为对现有方法的修改(或覆盖)。

使用外部代码编辑器来编写 Java 代码

通过 /methods 指令弹出文本框,在文本框进行代码的编写。

image-20220210162517736

从外部文件加载源代码

通过 /open 命令打开指定路径下的 Java 文件,并自动执行,在命令行输出结果。

/open D:/HelloWorld.java

没有受检异常(编译时异常)

下面代码本来应该强迫我们捕获一个 IOException,但却没有出现。因为 JShell 在后台为我们隐藏了。

URL url = new URL("http://notes.youngkbt.cn");

退出 JShell

/exit

多版本兼容jar包

官方 Feature:238:

使用说明

当一个新版本的 Java 出现的时候,你的库用户要花费数年时间才会切换到这个新的版本。这就意味着 库得去向后兼容你想要支持的最老的 Java 版本(许多情况下就是 Java 6 或者 Java7) 。这实际上意味着未来的很长一段时间,你都不能在库中运用 Java 9 所提供的新特性。幸运的是,多版本兼容 jar 功能能让你创建仅在特定版本的 Java 环境中运行库程序选择使用的 Class 版本

举例 1:

jar root  - A.class  // 旧版本  - B.class  // 旧版本  - C.class  - D.class  - META-INF    - versions      - 9        - A.class  // Java 9 版本        - B.class  // Java 9 版本

说明:在上述场景中,root.jar 可以在 Java 9 中使用,不过 A 或 B 类使用的不是顶层的 root.A 或 root.B 这两个 Class,而是处在 META-INF/versions/9 下面的这两个。这是 特别为 Java 9 准备的 Class 版本,可以运用 Java 9 所提供的特性和库。同时,在早期的 Java 诸版本中使用这个 JAR 也是能运行的,因为 较老版本的 Java 只会看到顶层的 A 类或 B 类

举例 2:

jar root  - A.class  // 旧版本  - B.class  // 旧版本  - C.class  - D.class  - META-INF    - versions      - 9        - A.class  // Java 9 版本        - B.class  // Java 9 版本        - 10          - A.class // Java 10 版本

这样一来,旧版本使用上方的 ABCD Class 文件,而 Java 9 使用自己的 AB Class 文件,Java 10 则使用自己的 A Class。

官方说明:

By this scheme, it is possible for versions of a class designed for a later Java platform release to override the version of that same class designed for an earlier Java platform release.

使用举例

看视频更清楚:https://www.bilibili.com/video/BV1v4411U7Sk?p=32

步骤一:提供必要的类

Java 8 环境

在指定目录(E:\teach\01_Java9\multijar\src\main\java\cn\youngkbt)下提供如下的类:

Generator.java

public class Generator {    public Set<String> createStrings() {        Set<String> strings = new HashSet<String>();        strings.add("Java");        strings.add("8");        return strings;   } }

Application.java

public class Application {    public static void testMultiJar() {        com.youngkbt.Generator gen = new Generator();        System.out.println("Generated strings: " +                           gen.createStrings());   } }

Java 9 环境

在如下目录(E:\teach\01_Java9\multijar\src\main\java-9\cn\youngkbt)下提供同名的类:

Generator.java

public class Generator {    public Set<String> createStrings() {        return Set.of("Java", "9");   } }

步骤二:打包

打包 Java 8 环境的代码,指令如下:

javac -d build --release 8 src/main/java/com/youngkbt/*.java

打包 Java 9 环境的代码,指令如下:

javac -d build9 --release 9 src/main/java-9/com/youngkbt/*.java

最后打包整个多版本 jar 包,指令如下:

jar --create --main-class=Application --file multijar.jar -C build . --release 9 -C build9 .

步骤三:进行测试

将生成的多版本 jar 包分别放到 Java 8 和 9 环境下运行,就会发现得到的结果不一样。

Generated strings: Java 8 Generated strings: Java 9

语法改进 - 接口的私有方法

官方 Feature:213:

使用说明

Java 8 中规定接口中的方法除了抽象方法之外,还可以定义静态方法和默认的方法。一定程度上,扩展了接口的功能,此时的接口更像是一个抽象类。

在 Java 9 中,接口更加的灵活和强大,连方法的访问权限修饰符都可以声明为 private 的了,此时方法将不会成为你对外暴露的 API 的一部分。

使用举例

/** * * 面试题:抽象类 和 接口 的异同? *   ① 二者的定义: a. 声明的方式   b. 内部的结构(jdk 7 ;jdk 8 ; jdk 9) *   ② 共同点:不能实例化;以多态的方式使用 *   ③ 不同点:单继承;多实现 */ interface MyInterface {    //jdk 7 : 只能声明全局常量(public static final)和抽象方法(public abstract)    void normalInterfaceMethod(); ​    // jdk 8 : 声明静态方法 和 默认方法    public static void method1(){        System.out.println("method1");   } ​    default void methodDefault1() {        init();   }    // 私有方法    private void init() {        System.out.println("默认方法中的通用操作");   } } class MyInterfaceImpl implements MyInterface{    @Override    public void normalInterfaceMethod() {        System.out.println("实现接口的方法");   } } ​ public class MyInterfaceTest{    public static void main(String[] args) {        MyInterfaceImpl impl = new MyInterfaceImpl();        impl.methodDefault1();        // impl.init();// 不能调用   } }

语法改进 - 钻石操作符使用升级

使用说明

我们将能够与匿名实现类共同使用钻石操作符(diamond operator) 在 Java 8 中如下的操作是会报错的(第 2 行结尾有 {}):

private List<String> flattenStrings(List<String>... lists) {    Set<String> set = new HashSet<>(){};    for(List<String> list : lists) {        set.addAll(list);   }    return new ArrayList<>(set); }

编译报错信息:

'<>' cannot be used with anonymous classes

使用举例

public class DiamondOperatorTest { ​    @Test    public void testDiamondOperator(){        diamondOperator();   } ​    public void diamondOperator(){        // 创建一个继承于 HashSet 的匿名子类的对象        Set<String> set = new HashSet<>(){};// 编译通过        set.add("MM");        set.add("JJ");        set.add("GG");        set.add("DD"); ​        for(String s : set){            System.out.println(s);       } ​   } }

语法改进 - try语句

在 Java 8 之前,我们习惯于这样处理资源的关闭:

public void testTry1(){    InputStreamReader reader = null;    try{        reader = new InputStreamReader(System.in);        //流的操作        reader.read();   }catch (IOException e){        e.printStackTrace();   }finally{        if(reader != null){            try {                reader.close();           } catch (IOException e) {                e.printStackTrace();           }       }   } }

Java 8 中,可以实现资源的自动关闭,但是要求执行后必须关闭的所有资源必须在 try 子句中初始化,否则编译不通过。如下例所示:

public void testTry2(){    try(InputStreamReader reader = new InputStreamReader(System.in)){   }catch (IOException e){        e.printStackTrace();   } }

Java 9 中,用资源语句编写 try 将更容易,我们可以在 try 子句中使用已经初始化过的资源,此时的资源是 final 的

public void testTry3(){    InputStreamReader reader = new InputStreamReader(System.in);    OutputStreamWriter writer = new OutputStreamWriter(System.out);    try(reader;writer){        // reader 是 final 的,不可再被赋值        // reader = null;   }catch (IOException e){        e.printStackTrace();   } }

语法改进 - 下划线使用的限制

UnderScore(下划线)。

在 Java 8 中,标识符可以独立使用 _ 来命名:

String _ = "hello"; System.out.println(_);

但是,在 Java 9 中规定 _ 不再可以单独命名标识符了,如果使用, 会报错:

As of Java 9, '_' is a keyword, and may not be used as an identifier

String存储结构变更

官方 Feature:JEP 254:

产生背景

String 类的当前实现将字符存储在字符数组,每个字符使用两个字节(16 位)。数据从许多不同的应用程序收集的数据表明字符串是堆使用的主要组成部分,而且最重要的是对象仅包含拉丁字母 1。这样的角色只需要一个字节的存储空间,因此占内部字符数组空间的一半许多这样的字符串对象将被闲置。

使用说明

我们建议更改 String 类的内部表示形式从 UTF-16 字符数组到字节数组再加上编码标志领域新的字符串类将存储编码为 ISO-8859-1 / 拉丁语-1(每个字符一个字节),或作为 UTF-16(两个字节每个字符),基于字符串的内容。编码标志将指示使用哪种编码。

结论:String 再也不用 char[] 来存储啦,改成了 byte[] 加上编码标记,节约了一些空间。

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {    @Stable    private final byte[] value; }

StringBuffer与StringBuilder

那 StringBuffer 和 StringBuilder 是否仍无动于衷呢?

与字符串相关的类,如 AbstractStringBuilder、StringBuilder、StringBuffer 将更新为使用相同的表示形式。

结论:AbstractStringBuilder、StringBuilder、StringBuffer 底层也变成了 byte[]

/** * String:jdk 8 及之前:底层使用char[]存储;jdk 9 : 底层使用byte[] (encoding flag) * StringBuffer:jdk 8 及之前:底层使用char[]存储;jdk 9 : 底层使用byte[] * StringBuilder:jdk 8 及之前:底层使用char[]存储;jdk 9 : 底层使用byte[] * * String:不可变的字符序列; * StringBuffer:可变的字符序列;线程安全的,效率低; * StringBuilder:可变的字符序列;线程不安全的,效率高(jdk 5.0) * */

集合工厂方法 - 快速创建只读集合

官方 Feature:269:

产生背景

要创建一个 只读、不可改变 的集合,必须构造和分配它,然后添加元素,最后包装成一个不可修改的集合。

Java 8 环境

比如使用 Collections 提供的 unmodifiableList 方法可以创建 只读、不可改变 的 List 集合:

public void test() {    List<String> namesList = new ArrayList <>();    namesList.add("Joe");    namesList.add("Bob");    namesList.add("Bill");    namesList = Collections.unmodifiableList(namesList);    System.out.println(namesList); }

缺点:我们一下写了五行。即:它不能表达为单个表达式。

当然,我们也可以稍微简单点处理:

public void test() {    // List    List<String> list = Collections.unmodifiableList(Arrays.asList("a", "b", "c"));    // Set    Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("a", "b", "c")));    // Map:如下操作不适用于 jdk 8 及之前版本,适用于 jdk 9    Map<String,Integer> map = Collections.unmodifiableMap(new HashMap<>(){ { put("a", 1); put("b", 2); put("c", 3); } });    map.forEach((k,v) -> System.out.println(k + ":" + v)); }

Java 9 因此 引入了方便的方法,这使得类似的事情更容易表达。

使用说明

Java 9 给各个集合引入了 of() 静态方法,该方法代替 Collections.unmodifiableXXX 创建出 只读、不可改变 的集合。

image-20220210181532432

List firsnamesList = List.of("Joe", "Bob", "Bill");

调用集合中静态方法 of(),可以将不同数量的参数传输到此工厂方法中。此功能可用于 Set 和 List,也可用于 Map 的类似形式。此时得到的集合,是不可变的:在创建后,继续添加元素到这些集合会导致 UnsupportedOperationException

java.lang.UnsupportedOperationException

由于 Java 8 中接口方法的实现,可以直接在 List,Set 和 Map 的接口内定义这些方法,便于调用。

注意:Set.of() 里不能传重复的元素,虽然正常情况下 Set 类会自动把重复的元素去掉,但是 of() 方法遇到重复元素是会报错的,如:

Set<Integer> set = Set.of(100, 200, 300, 100);

使用举例

public void test(){    // List    List<Integer> list = List.of(1, 2, 3); ​    list.forEach(System.out::println); ​    // Set    Set<Integer> set = Set.of(2, 3, 4); ​    // Map    // 创建只读集合的方式一:    Map<String, Integer> map = Map.of("Tom", 23, "Jerry", 22, "Lilei", 12, "HanMeimei", 18); ​    // 创建只读集合的方式二:    Map<String, Integer> map1 = Map.ofEntries(Map.entry("Tom", 23), Map.entry("Jerry", 21)); ​    System.out.println(map1); }

增强的Stream API

使用说明

Java 的 Steam API 是 Java 标准库最好的改进之一,让开发者能够快速运算,从而能够有效的利用数据并行计算。Java 8 提供的 Steam 能够利用多核架构实现声明式的数据处理。

在 Java 9 中,Stream API 变得更好,Stream 接口中添加了 4 个新的方法:dropWhiletakeWhileofNullable,还有个 iterate 方法的新重载方法,可以让你提供一个 Predicate(判断条件)来指定什么时候结束迭代。

除了对 Stream 本身的扩展,Optional 和 Stream 之间的结合也得到了改进。现在可以通过 Optional 的新方法 stream() 将一个 Optional 对象转换为一个(可能是空的)Stream 对象。

使用举例

takeWhile() 的使用

用于从 Stream 中获取一部分数据,接收一个 Predicate 来进行选择。在有序的 Stream 中,takeWhile 返回从开头开始的尽量多的元素。

public void test1(){    List<Integer> list = Arrays.asList(45,56,33,77,44,98,76,78,33); ​    Stream<Integer> stream = list.stream(); ​    stream.takeWhile(x -> x < 70).forEach(System.out::println); ​    System.out.println(); ​    List<Integer> list1 = Arrays.asList(1,2,3,4,5,6,7,8);    list1.stream().takeWhile(x -> x < 5).forEach(System.out::println); // 输出:45 56 33 }

也就是找到不符合条件的元素后,停止寻找,虽然后面还有一些元素符合条件,也不会获取。

dropWhile() 的使用

dropWhile 的行为与 takeWhile 相反,返回剩余的元素。

public void test2(){    List<Integer> list = Arrays.asList(45,56,33,77,44,98,76,78,33); ​    Stream<Integer> stream = list.stream(); ​    stream.dropWhile(x -> x < 70).forEach(System.out::println); ​    System.out.println(); ​    List<Integer> list1 = Arrays.asList(1,2,3,4,5,6,7,8);    list1.stream().dropWhile(x -> x < 5).forEach(System.out::println); // 输出 77 44 98 76 78 33 }

也就是找到符合条件的元素后,停止寻找,虽然后面还有一些元素不符合条件,也不会获取。

ofNullable() 的使用

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

``` public void test3(){

   Stream stream1 = Stream.of(1, 2, 3, null);    stream1.forEach(System.out::println); ​    System.out.println(); ​    // 如果只有单个元素,此元素不能为 null.否则报 NullPointerException    // Stream stream2 = Stream.of(null); ​    // jdk 9:新增 ofNullable(T t)    Stream stream3 = Stream.ofNullable("Tom");    System.out.println(stream3.count()); // 1 ​    Stream stream4 = Stream.ofNullable(null);    System.out.println(stream4.count()); // 0 } ```

iterator() 重载的使用

Stream 的实例化:

  • 通过集合的 stream()

  • 通过数组工具类:Arrays.stream()

  • Stream 中静态方法:

    of()

    • iterator() / generate()

原来的 Java 8 控制终止方式加 limit:

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);

Optional 类中 stream() 的使用

// Optional 类中提供了转换为 Stream 的方法:stream() public void test1() {    List<String> list = new ArrayList<>();    list.add("Tom");    list.add("Jerry");    list.add("Tim"); ​    Optional<List<String>> optional = Optional.ofNullable(list); ​    Stream<String> stream = optional.stream().flatMap(x -> x.stream());    // System.out.println(stream.count()); ​    stream.forEach(System.out::println); ​ ​ }

输出流直接转输入流

InputStream 终于有了一个非常有用的方法:transferTo,可以用来将数据直接传输到 OutputStream,这是在处理原始数据流时非常常见的一种用法。

如下示例:

public void test2() throws IOException {    ClassLoader classLoader = ClassLoader.getSystemClassLoader();    InputStream inputStream = classLoader.getResourceAsStream("file.txt"); // 包路径下    try (OutputStream outputStream = new FileOutputStream("file2.txt")) {        inputStream.transferTo(outputStream); // 把输入流中的所有数据直接自动地复制到输出流中   }    inputStream.close(); }

这样我们就会发现在 src 目录下发现 file2.txt 文件,并且内容与 file.txt 一样。

多分辨率图像API

官方 Feature

产生背景

在 Mac 上,JDK 已经支持视网膜显示,但在 Linux 和 Windows 上,它并没有。在那里,Java 程序在当前的高分辨率屏幕上可能看起来很小,不能使用它们。这是因为像素用于这些系统的大小计算(无论像素实际有多大)。毕竟,高分辨率显示器的有效部分是像素非常小。

JEP 263 以这样的方式扩展了 JDK,即 Windows 和 Linux 也考虑到像素的大小。为此,使用比现在更多的现代 API:Direct2D for Windows 和 GTK +,而不是 Xlib for Linux 图形,窗口和文本由此自动缩放

JEP 251 还提供处理多分辨率图像的能力,即 包含不同分辨率的相同图像的文件。根据相应屏幕的 DPI 度量,然后以适当的分辨率使用图像。

使用说明

  • 新的 API 定义在 java.awt.image 包下
  • 将不同分辨率的图像封装到一张(多分辨率的)图像中,作为它的变体
  • 获取这个图像的所有变体
  • 获取特定分辨率的图像变体,表示一张已知分辨率单位为 DPI 的特定尺寸大小的逻辑图像,并且这张图像是最佳的变体
  • 基于当前屏幕分辨率大小和运用的图像转换算法,java.awt.Graphics 类可以从接口 MultiResolutionImage 获取所需的变体
  • MultiResolutionImage 的基础实现是 java.awt.image.BaseMultiResolutionImage

全新的HTTP客户端API

官方 Feature:110:

使用说明

HTTP,用于传输网页的协议,早在 1997 年就被采用在目前的 1.1 版本中。直到 2015 年,HTTP2 才成为标准

image-20220210184253143

HTTP/1.1 和 HTTP/2 的主要区别是如何在客户端和服务器之间构建和传输数据。HTTP/1.1 依赖于请求/响应周期HTTP/2 允许服务器 push 数据:它可以发送比客户端请求更多的数据。这使得它可以优先处理并发送对于首先加载网页至关重要的数据。

Java 9 中有新的方式来处理 HTTP 调用。它提供了一个新的 HTTP 客户端(HttpClient),它将 替代仅适用于 blocking 模式的 HttpURLConnection(HttpURLConnection 是在 HTTP 1.0 的时代创建的,并使用了协议无关的方法),并提供 对 WebSocket 和 HTTP/2 的支持

此外,HTTP 客户端还提供 API 来处理 HTTP/2 的特性,比如流和服务器推送等功能。

全新的 HTTP 客户端 API 可以从 jdk.incubator.httpclient 模块中获取。因为在默认情况下,这个模块是不能根据 classpath 获取的,需要使用 add modules 命令选项配置这个模块,将这个模块添加到 classpath 中。

使用举例

public void testURL() {    //jdk 9 中 使用 HttpClient 替换原有的 HttpURLConnection    try {        HttpClient client = HttpClient.newHttpClient(); ​        HttpRequest req = HttpRequest.newBuilder(URI.create("http://www.youngkbt.com")).GET().build(); ​        HttpResponse<String> response = null;        response = client.send(req, HttpResponse.BodyHandler.asString());        System.out.println(response.statusCode());        System.out.println(response.version().name());        System.out.println(response.body()); ​   } catch (IOException e) {        e.printStackTrace();   } catch (InterruptedException e) {        e.printStackTrace();   } }

Deprecated的相关API

官方 Feature

使用说明

Java 9 废弃或者移除了几个不常用的功能。其中最主要的是 Applet API,现在是标记为废弃的。随着对安全要求的提高,主流浏览器已经取消对 Java 浏览器插件的支持。HTML5 的出现也进一步加速了它的消亡。开发者现在可以使用像 Java Web Start 这样的技术来代替 Applet,它可以实现从浏览器启动应用程序或者安装应用程序。

同时,appletviewer 工具也被标记为废弃。

智能Java编译工具

官方 Feature

使用说明

智能 java 编译工具(sjavac) 的第一个阶段始于 JEP139 这个项目,用于 在多核处理器情况下提升 JDK 的编译速度。如今,这个项目已经进入第二阶段,即 JEP199,其目的是改进 Java 编译工具,并 取代目前 JDK 编译工具 javac,继而成为 Java 环境默认的通用的智能编译工具

JDK 9 还 更新了 javac 编译器 以便能够将 Java 9 代码编译运行在低版本 Java 中。

统一的JVM日志系统和垃圾回收器

官方 Feature:

使用说明

日志是解决问题的唯一有效途径:曾经很难知道导致 JVM 性能问题和导致 JVM 崩溃的根本原因。不同的 JVM 日志的碎片化和日志选项(例如:JVM 组件对于日志使用的是不同的机制和规则),这使得 JVM 难以进行调试

解决该问题最佳方法:对所有的 JVM 组件引入一个单一的系统,这些 JVM 组件支持细粒度的和易配置的 JVM 日志。

Java 9 已经将 G1 作为默认的垃圾回收器,统一处理新生代和老年代的垃圾回收。

javadoc的HTML5支持

官方 Feature

使用说明

JDK 8:生成的 Java 帮助文档是在 HTML 4 中,而 HTML 4 已经是很久的标准了。

JDK 9:javadoc 的输出,现在符合兼容 HTML 5 标准。

下图是 Java 8 中生成的 HTML 页面,如果想要找到一些类文档,必须在 Google 中搜索。

image-20220210201349926

下图是在 Java 9 中,添加了一个搜索框。

image-20220210201401665

Javascript引擎升级 - Nashorn

官方 Feature

使用说明

Nashorn 项目在 JDK 9 中得到改进,它为 Java 提供轻量级的 Javascript 运行时。Nashorn 项目跟随 Netscape 的 Rhino 项目,目的是为了在 Java 中实现一个高性能但轻量级的 JavaScript 运行时。Nashorn 项目使得 Java 应用能够嵌入 JavaScript。它在 JDK 8 中为 Java 提供一个 JavaScript 引擎。

JDK 9 包含一个用来解析 Nashorn 的 ECMAScript 语法树的 API。这个 API 使得 IDE 和服务端框架不需要依赖 Nashorn 项目的内部实现类,就能够分析 ECMAScript 代码。

java的动态编译器

官方 Feature:

产生背景

Oracle 一直在努力提高 Java 启动和运行时性能,希望其能够在更广泛的场景达到或接近本地语言的性能。但是,直到今天,谈到 Java,很多 C/C++ 开发者还是会不屑地评价为 启动慢,吃内存

简单说,这主要是因为 Java 编译产生的类文件是 Java 虚拟机可以理解的二进制代码,而不是真正的可执行的本地代码,需要 Java 虚拟机 进行解释和编译,这带来了额外的开销

使用说明

JIT(Just-in-time)编译器可以在运行时将热点编译成本地代码,速度很快。但是 Java 项目现在变得很大很复杂,因此 JIT 编译器需要 花费较长时间才能热身完,而且有些 Java 方法还没法编译,性能方面也会下降。AOT 编译就是为了解决这些问题而生的。

在 JDK 9 中,AOT(JEP 295:Ahead-of-Time Compilation)作为实验特性被引入进来,开发者可以利用新的 jaotc 工具将重点代码转换成类似类库一样的文件。虽然仍处于试验阶段,但这个功能使得 Java 应用在被虚拟机启动之前能够先将 Java 类编译为原生代码。此功能旨在改进小型和大型应用程序的启动时间,同时对峰值性能的影响很小。

但是 Java 技术供应商 Excelsior 的营销总监 Dmitry Leskov 担心 AOT 编译技术不够成熟,希望 Oracle 能够 等到 Java 10 时有个更稳定版本才发布。

另外 JVMCI(JEP 243:Java-Level JVM Compiler Interface)等特性,对于整个编程语言的发展,可能都具有非常重要的意义,虽然未必引起了广泛关注。目前 Graal Core API 已经被集成进入 Java 9,虽然还只是初始一小步,但是完全用 Java 语言来实现的可靠的、高性能的动态编译器,似乎不再是遥不可及,这是 Java 虚拟机开发工程师的福音。

与此同时,随着 Truffle 框架和 Substrate VM 的发展,已经让个别信心满满的工程师高呼 One VM to Rule Them All!, 也许就在不远的将来 Ploygot 以一种另类的方式成为现实。

总结

在java9中看不到什么

一个标准化和轻量级的 JSON API

一个标准化和轻量级的 JSON API 被许多 Java 开发人员所青睐。但是由于资金问题无法在 Java 9 中见到,但并不会削减掉。Java 平台首席架构师 Mark Reinhold 在 JDK 9 邮件列中说:「这个 JEP 将是平台上的一个有用的补充,但是在计划中,它并不像 Oracle 资助的其他功能那么重要,可能会重新考虑 JDK 10 或更高版本中实现。」

新的货币 API

对许多应用而言货币价值都是一个关键的特性,但 JDK 对此却几乎没有任何支持。严格来讲,现有的 java.util.Currency 类只是代表了当前 ISO 4217 货币的一个数据结构,但 并没有关联的值或者自定义货币。JDK 对货币的运算及转换也没有内建的支持,更别说有一个能够代表货币值的标准类型了。

此前,Oracle 公布的 JSR 354 定义了一套新的 Java 货币 API:JavaMoney,计划会在 Java 9 中正式引入。但是目前没有出现在 JDK 9 中。

不过,如果你用的是 Maven 的话,可以做如下的添加,即可使用相关的 API 处理货币:

<dependency>    <groupId>org.javamoney</groupId>    <artifactId>moneta</artifactId>    <version>0.9</version> </dependency>

代码参考,可以访问 https://github.com/JavaMoney,里面已经给出了使用说明和示例。

展望

随着云计算和 AI 等技术浪潮,当前的计算模式和场景正在发生翻天覆地的变化,不仅对 Java 的发展速度提出了更高要求,也深刻影响着 Java 技术的发展方向。传统的大型企业或互联网应用,正在被云端、容器化应用、模块化的微服务甚至是函数(FaaS,Function-as-a-Service)所替代

Java 虽然标榜面向对象编程,却毫不顾忌的 加入面向接口编程思想,又扯出 匿名对象 之概念,每增加一个新的东西,对 Java 的根本所在的面向对象思想的一次冲击。反观 Python,抓住面向对象的本质,又能在函数编程思想方面游刃有余。Java 对标 C/C++,以抛掉内存管理为卖点,却又陷入了 JVM 优化的噩梦。选择比努力更重要,选择 Java 的人更需要对它有更清晰的认识。

Java 需要在新的计算场景下,改进开发效率。这话说的有点笼统,我谈一些自己的体会,Java 代码虽然进行了一些类型推断等改进,更易用的集合 API 等,但仍然给开发者留下了过于刻板、形式主义的印象,这是一个长期的改进方向。

Supongo que te gusta

Origin blog.csdn.net/zhiqi_l163991102/article/details/131414766
Recomendado
Clasificación