Java 9即将来临,NetBeans 9也即将来临。在本文中,我们将看到NetBeans 9 Early Access为开发人员提供的支持,以帮助他们构建Java 9兼容的应用程序。
Java 9提供了许多(大约90种) 新功能,包括Modules和JShell,Read-Eval-Print-Loop(REPL)等。 您可以在本文末尾的参考资料中找到更多信息,尤其是有关NetBeans 9支持JDK 9的链接 。
在本文中,我们将学习如何:
- 从源代码下载并构建Java 9 EA
- 从源代码下载并构建NetBeans 9 EA
- 使用NetBeans 9 EA中的JShell REPL
- 使用NetBeans 9 EA创建和构建模块以及它们之间的依赖关系,使我们的生活更轻松
1.构建OpenJDK 9 EA
OpenJDK是任何JDK增强建议 (JEP)甚至Java规范请求 (JSR)的参考实现。 您可能有很多原因,为什么要从源代码构建它,而不下载预构建的二进制文件之一 ,例如:
- 强制您从源代码构建开源项目的公司政策
- 您希望获得最新的早期访问OpenJDK
- 您想要为您的特殊平台构建
当然,您可以通过下载预构建的二进制文件来继续本文。 但是,如果您想学习如何构建JDK 9,请继续阅读。 构建自己的JDK映像具有许多优势,例如,您不需要安装任何东西或进行修改,例如Windows注册表或MacOS上的Java Preferences面板都可以破坏当前的默认JDK(请参见例如本文 )。 可执行文件将始终在同一位置创建,并且通过更新存储库并重新构建源代码,您始终可以拥有最新版本。
您可以下载最新的OpenJDK 9二进制文件此页面。 资料可以在Mercurial资料库中找到。 例如OpenJDK 9仓库在这里 ; 如果希望参与项目,那么Jigsaw回购项目将包含模块化实现的主要内容。 在下面,您将获得有关如何构建OpenJDK 9的一些技巧。
- 克隆JDK 9 Master mercurial存储库。
- 阅读
README
或README-builds.html
文件以获取更多说明。 - 开始构建之前的下一步是执行:
bash get_source.sh
或./get_source.sh
如果您的shell已经是bash)。 在每次更新后调用此命令非常重要。 原因是hg update
仅更新主存储库。 OpenJDK包含许多需要更新的Mercurial存储库。 这可以通过get_source.sh
命令来完成。 -
./configure --disable-warnings-as-errors
-
sudo make all
上面的命令构建了OpenJDK的最新版本。 如果您希望构建OpenJDK的早期版本,则需要遵循以下提示:
-
hg up [tag]
例如jdk-9+147
-
cd corba
-
hg up [tag]
例如jdk-9+147
- 对目录重复步骤2和3:
hotspot, jaxp, jaxws, jdk, langtools, nashorn
-
./configure --disable-warnings-as-errors
-
sudo make clean
-
sudo make all
二进制文件是在build/<platform_dir>/jdk
中创建的,例如,如果您在build/macosx-x86_64-normal-server-release/jdk
使用Mac。
2.构建和配置NetBeans 9 EA
您可以从http://wiki.netbeans.org/ JDK9Support下载具有JDK 9支持的最新NetBeans或从以下来源进行构建:
-
hg clone http://hg.netbeans.org/main
-
cd main
-
hg clone http://hg.netbeans.org/main/contrib
-
cd ..
-
ant
二进制文件在nbbuild/netbeans
创建。 将其配置为与JDK 8或JDK 9 EA( etc/netbeans.conf
)一起运行。 jshell
,要启用jshell
,您需要使用JDK 9 EA设置NetBeans 9。 因此,编辑etc/netbeans.conf
指向您在步骤1中构建的JDK 9 EA:
netbeans_jdkhome="<path to OpenJDK 9 EA>/build/<platform_dir>/jdk"
请备份此文件,因为下次构建NetBeans时,它将被覆盖,因此您必须再次进行此修改。
通过发出以下命令来启动netbeans: bin/netbeans
或bin\netbeans.exe
具体取决于您的平台。 通过工具 | NetBeans将最新的JDK 9 EA构建注册为Java平台 。 Java 平台| 添加平台 (参见图1),然后选择在步骤1中构建的OpenJDK 9 EA。
3. NetBeans 9 EA中的JShell支持
如果使用JDK 9实现启动NetBeans 9,则可以从菜单|工具|菜单中访问JShell 。 打开Java Platform Shell 。 JShell的工作原理与命令行相同,此外,NetBeans快捷方式也可以使用它(例如sout --> (tab)
)。 您可以在参考资料中阅读有关JShell的更多信息。
4. NetBeans 9 EA中的模块支持
NetBeans 9 EA提供了许多好处,可以帮助您进行模块化项目。 在继续之前,必须在“ 工具” |“设置”中设置JDK 9 EA平台。 Java 平台| 添加平台并选择您在步骤1中构建的OpenJDK 9 EA二进制文件的路径。
要将现有项目转换为模块,您需要执行两项任务:
- 在项目属性中将项目设置为与JDK 9兼容:
- 在Libraries中,将Java Platform设置为JDK 9 EA Java平台(图3)。
- 在Sources中,将Source / Binary Format设置为JDK 9 (图4)。
- 在您的项目中添加一个Java模块信息(即模块描述符
module-info.java
)(见图5):- 档案| 新档案…| Java(类别)| Java模块信息(文件类型)
module-info.java
必须始终位于NetBeans 9中Java项目的根目录中。这是NetBeans 9中的一项限制,而不是JDK 9中的一项限制。唯一的例外是,我们可以在一个目录中包含多个module-info.java
文件。一个Java项目就是我们进行单元测试的时候。 您可以在Test Packages
添加module-info.java
文件。
但是,让我们通过在NetBeans 9 EA中实现项目Jigsaw的快速入门指南来了解NetBeans 9 EA模块的支持。
我的第一个使用NetBeans 9的模块化应用程序
第一个示例是名为com.greetings
的模块,该模块仅显示“ Greetings!”。 该模块包含两个源文件:模块声明( module-info.java
)和主类。
按照约定(模块名称可以是Java限定的标识符),模块的源代码位于模块名称的目录中(在本例中为com.greetings
),即使这不是必需的。
通过执行以下步骤在NetBeans中创建一个新的Java项目:
- 档案| 新项目…
- 选择Java (类别)和Java应用程序 (项目),然后单击Next。
- 在下一页中,选择一个“项目位置”并输入“ com.greetings”作为项目名称,这是因为遵循本教程的约定,但是也可以使用常规的Java项目名称,例如“ Greetings”。 将主类重命名为
com.greetings.Main
。 点击完成 。
您应该看到一个名为com.greetings
的Java项目,并且在其中包含com.greetings.Main
类,其中包含main()
方法。 像这样修改它:
com.greetings.Main
package com.greetings;/** @author javacodegeeks */
public class Main {/** @param args the command line arguments */public static void main(String[] args) {System.out.println("Greetings!");}
}
要将Java Project转换为模块, module-info.java
按照前面所述添加module-info.java
,即,右键单击项目名称,然后选择File | 新档案…| Java(类别)| Java模块信息(文件类型) 。
在项目的根包中创建一个空的module-info.java
。 按照Jigsaw教程重命名,如下所示:
com.greetings.module-info.java
module com.greetings {
}
您必须清理并构建项目才能使模块重命名生效并成功运行。 您应该看到消息“问候!” 在输出窗口中。
使用NetBeans,您无需关心命令行语法以及javac
和java
命令的参数。 这些由NetBeans IDE负责。
添加依赖项
第二个示例更新模块声明,以声明对模块org.astro
的依赖。 模块org.astro
导出API包org.astro
。
按照上一org.astro
的步骤创建一个名为org.astro
的新Java项目。 这次不要创建Main Class。 创建项目后,右键单击它,然后选择“ 新建” |“新建”。 Java类…输入World
作为类名,输入org.astro
作为包名,然后单击Finish 。 像这样更新新创建的类:
org.astro.World.java
package org.astro;/** @author javacodegeeks */
public class World {public static String name() {return "world";}
}
添加module-info.java
就像我们对以前那样com.greetings
。
org.astro.module-info.java
module org.astro {
}
不要忘记清理和构建模块重命名才能生效。
现在,我们需要从com.greetings
模块到org.astro
模块添加一个依赖关系,以便使用其方法World.name()
。 但是在此之前, org.astro
必须导出包含此方法的软件包。 这两个动作都需要在两个模块的module-info.java
文件中进行。 NetBeans 9为您提供了有关如何执行操作的有用提示。
- 打开
org.astro
的module-info.java
并在方括号内键入Ctrl-Space
。 如示于图6.选择一个弹出菜单将出现,显示可用的命令exports
并通过键入继续org.astro
它是包名到出口。 NetBeans在键入时为您提供提示。 保存module-info.java
您会注意到“ 项目”选项卡中软件包的锁定图标变为打开的锁定。 - 打开
com.greetings
的module-info.java
并在括号内按照上述步骤输入requires org.astro
的命令,这一次引用的是模块名称而不是程序包名称(另一个原因是为什么Jigsaw项目的快速入门指南并不是那么成功,因为它难以区分包和模块名。 但是,NetBeans抱怨module not found
错误消息module not found
。 为了使NetBeans能够找到该模块,还需要执行一步。 - 右键单击
com.greetings
Java项目的Libraries文件夹,com.greetings
从弹出菜单中选择“ 添加项目 ”。 选择org.astro
Java项目,然后单击添加项目JAR文件 。 错误消失了。 另一种执行相同操作的方法是右键单击项目com.greetings
然后从弹出菜单中选择“ 属性 ”。 在“项目属性”对话框中,单击“ 库 ”类别,然后单击“ 模块路径”旁边的+号。 选择“ 添加项目”,然后选择“org.astro
Java项目”,然后单击“ 添加项目JAR文件” 。 请记住,如果您尝试添加循环依赖关系(例如,从`org.astro`到com.greetings
),则NetBeans将显示一个对话框,显示以下消息: 无法添加循环引用 (请参见图7)。 - 像这样修改
Main.main()
方法,然后清理并构建两个模块。
com.greetings.Main.java
package com.greetings;
import org.astro.World;
/** @author javacodegeeks */
public class Main {/** @param args the command line arguments */public static void main(String[] args) {System.out.format("Greetings %s!%n", World.name());}
}
当您粘贴新的System.out.format(...)
语句时,NetBeans会识别World
类,并建议添加import
语句。 这是唯一可能的,因为您已经更新了两个模块的module-info.java
文件,如下所示:
com.greetings.module-info.java
module org.astro {exports org.astro;
}
org.astro.module-info.java
module com.greetings {requires org.astro;
}
NetBeans 9 EA为您提供了依赖性的可视化表示(模块图)。 只需在org.greetings module-info.java
编辑器中单击Graph按钮,即可看到模块依赖关系的漂亮图表,如下图所示。
运行com.greetings
模块以查看输出: Greetings world!
如预期的那样。
与项目Jigsaw的原始快速入门指南进行比较,以了解使用NetBeans IDE节省了多少键入内容。
包装模块
使用NetBeans打包模块非常容易。 右键单击项目com.greetings
然后从弹出菜单中选择“ 属性 ”。 在Project Properties对话框(图9)中,单击Build下的Packaging类,并选择Create JLink distribution和Create Launcher ,然后单击OK 。 下次清洁和构建NetBeans时,将在com.greetings
Java项目的dist
文件夹内生成Java运行时映像,其中仅包含运行com.greetings
所需的JDK模块(即,仅java.base
模块)。 无需记住jlink
命令的语法。
Java模块化项目
正如我们一开始提到的,NetBeans 9每个Java项目仅允许一个模块。 但是,使用Java模块化项目,可以在这个特殊的Java项目中定义许多模块。 我们将使用Java模块化项目重新实现先前的模块。 这是一个基于ant的项目,其中包含多个模块并立即进行编译。
- 档案| 新项目…
- 选择Java (类别)和Java Modular Project (项目)(图10),然后单击Next。
- 输入
ModularGreetings
作为项目名称,然后单击完成。 - 右键单击新创建的项目,然后从弹出菜单中选择“ 新建模块 ”。
- 输入
com.greetings
作为模块名称,然后单击Finish 。 NetBeans会为此模块打开module-info.java
文件。 - 重复上一步以创建
org.astro
模块。 - 如4.2中所述在两个模块之间创建依赖关系
- 在每个模块的
classes
中创建(或从4.2中创建的先前模块中复制)软件包。 - 清洁并建造。 无需添加
org.astro
到的模块路径com.greetings
; 它是自动完成的。 - 运行项目以查看正确的输出:
Greetings world!
您会看到Java Modular Project具有许多优点,例如您不需要将其他项目显式添加到Module路径。 当您更新module-info.java
时,将自动完成此操作。
服务
松散耦合是指每个组件对其他独立组件的定义了解很少或不了解的系统。 这使得各个组件可以独立更改,而不会影响其他组件。
但是,让我们描述一个示例,以深入了解《 快速入门指南》的详细信息。
假设您有一个提供服务的提供者。 例如,这可以是AlertService
(提供各种系统或应用程序警报), CoordinatesProvider
(提供各种坐标系统,例如纬度/经度,GEOREF,UTM等), AlgorithmProvider
(提供针对问题的各种算法解决方案)等为了实现松散耦合,您可以为调用程序类提供一个接口,以隐藏其背后的实际实现。 调用者(或服务使用者)类不需要知道任何实际的实现。 他们只需要知道如何访问相关方法即可。 然后,在运行时以某种方式将实现提供给调用方类。 这样,只要Provider
接口不更改,实际的实现就可以在任何时候更改而无需调用方类知道。
有多种方法可以实现松散耦合。 这通常是由服务提供商完成的。 服务定位器设计模式提供了对服务的全局访问点,而无需将调用者(服务使用者)耦合到实现该服务的具体类上。 例如, Spring使用依赖注入 ( 控制反转的一种形式),NetBeans的RCP 模块化API使用Lookups和ServiceProviders等。Jigsaw使用Java 6中的ServiceLoader 。服务使用者和服务提供者类可以驻留在不同的模块中。
从拼图快速入门指南 ,模块com.socket
提供服务NetworkSocketProvider
为NetworkSocket
秒。 在两个不同的模块中提供了此服务的两种实现: org.fastsocket
和org.smartsocket
。 我们的服务使用者模块com.greetings
仅需要依赖com.socket
服务提供者模块, com.greetings
需要依赖服务实现模块。
让我们看看如何实现这一目标。 在NetBeans 9 EA中,如我们之前com.socket
,创建一个新的Java Project com.socket
,而不提供Main
类。
如《 快速入门指南》中所述,创建两个类com.socket.NetworkSocket
和com.socket.spi.NetworkSocketProvider
,添加新的module-info.java
以将项目转换为Java 9模块,并将这两个包导出到使用者类:
com.socket.module-info.java
module com.socket {exports com.socket;exports com.socket.spi;uses com.socket.spi.NetworkSocketProvider;
}
最后一条语句声明此模块向使用者提供com.socket.spi.NetworkSocketProvider
服务。 不要忘记清理并构建此模块,以使更改生效。
接下来,让我们创建org.fastsocket
模块。 与以前一样,按照快速入门指南中的描述创建两个类org.fastsocket.FastNetworkSocket
和org.fastsocket.FastNetworkSocketProvider
,添加一个新的module-info.java
以将项目转换为Java 9模块,并为com.socket
添加依赖项com.socket
模块:
org.fastsocket.module-info.java
module org.fastsocket {requires com.socket;provides com.socket.spi.NetworkSocketProviderwith org.fastsocket.FastNetworkSocketProvider;
}
最后一条语句提供com.socket.spi.NetworkSocketProvider
服务提供程序的实现。 请注意,这个模块不出口任何包!
但是,该项目包含编译错误。 你能说出为什么吗?
我们需要将com.socket
添加到modulepath中(如果您不记得该怎么做,请参考本文前面的内容)。 清理并构建以确保没有错误。
您可以重复上述步骤以类似的方式创建org.smartsocket
,但是,快速入门指南并没有这样做,因此,您可以根据需要创建自己的实现。
最后,创建消费者com.greetings
Java项目(使用另一个目录不乱用的com.greetings
模块我们在第一章4.1和4.2创建)与com.greetings.Main
类,从的内容复制快速入门指南 ,添加一个新的module-info.java
,将项目转换为Java 9模块,并向com.socket
添加一个依赖com.socket
:
com.greetings.module-info.java
module com.greetings {requires com.socket;
}
确保您添加了模块com.socket
并完成。 清理并构建,然后运行com.greetings
,您将看到一个错误:
运行时异常
Exception in thread "main" java.lang.RuntimeException: No service providers found!at com.socket/com.socket.NetworkSocket.open(NetworkSocket.java:19)at com.greetings/com.greetings.Main.main(Main.java:8)
为什么? 我们确实做了《 快速入门指南》中提到的内容。 NetBeans Java项目要求您在模块路径中添加org.fastsocket
(然后将其自动添加到module-info.java
),然后该异常消失:
org.fastsocket.FastNetworkSocket
class org.fastsocket.FastNetworkSocket
作为练习,您可以使用Java模块化项目重复上述操作。 在这里,你不需要添加服务提供者实现模块(如org.fastsocket
)到模块路径com.greetings
。
但是它是如何工作的呢? 拼图使用ServiceLoader定位各种服务提供者实现:
服务加载器
ServiceLoader<NetworkSocketProvider> sl= ServiceLoader.load(NetworkSocketProvider.class);
Iterator<NetworkSocketProvider> iter = sl.iterator();
sl.iterator()
将遍历org.fastsocket.FastNetworkSocketProvider
和com.smartsocket.SmartNetworkSocketProvider
(如果已实现)。
在后台, ServiceLoader创建一个提供程序配置文件,该文件存储在服务提供程序的JAR文件的META-INF/services
目录中。 配置文件的名称是服务提供者的完全限定的类名称,其中名称的每个组成部分均以句点( .
)分隔,而嵌套的类则以美元符号( $
)分隔。 换句话说, ServiceLoader
在模块的build/classes/META-INF/services/
文件夹(或dist/provider.jar
)内创建一个文本文件package.Provider
,其中包含实现类的标准名称,例如package.ProviderImpl
。
在我们的示例中, com.socket/build/classes/META-INF/services/
包含文本文件com.socket.spi.NetworkSocketProvider
,其中包含实现类org.fastsocket.FastNetworkSocketProvider
(和com.smartsocket.SmartNetworkSocketProvider
标准名称com.smartsocket.SmartNetworkSocketProvider
如果已实现)。
至少,如果使用Java 6 ServiceLoader
,这应该是“幕后”实现! 不幸的是,Java 9修改了ServiceLoader
的实现。
Java 6 ServiceLoader
具有许多限制:
- 它不是动态的(您无法在运行时安装/卸载插件/服务)
- 它会在启动时加载所有服务(因此需要更长的启动时间和更多的内存使用量)
- 无法配置; 有一个标准的构造函数,它不支持工厂方法
- 它不允许进行排名/排序,即我们无法选择要首先加载的服务(服务的排序是在发现时进行的)
此外,Java 9对Java 6 ServiceLoader
进行了如下修改:
- 没有相关服务; 新的基于模块的服务定位器没有相对行为
- 服务的订购(如发现)丢失
- 模块路径上的所有服务接口和实现均被展平为单个全局名称空间
- 服务加载没有可扩展性/可定制性; 服务层提供者必须预先提供可用服务的固定映射
- 多站点声明; 每个使用服务的模块还必须在模块描述符中声明正在使用该服务; 没有全局的全层服务注册表
换句话说,如果您搜索com.socket/build/classes/META-INF/services/
或com.socket/dist/com.socket.jar/META-INF/services/
您将一无所获。
NetBeans RCP改为提供ServiceProvider ,它没有上面提到的ServiceLoader
的缺点。 它是动态的,因此您可以在应用程序运行时插入/拔出模块,它不会在启动时加载所有服务,并允许您设置优先级(使用@ServiceProvider
批注的position
属性)。 不幸的是,它不适用于拼图。
5. NetBeans的进一步改进
要使程序包可用于其他模块,必须编辑module-info.java
并添加一个将程序包名称作为参数传递的exports
语句。 这将导致包图标更改为具有打开锁的图标,而不是已锁定的图标。
一个不错的捷径是能够右键单击一个包,然后选择一个动作Export Package ,如下图所示,该操作将相应地自动修改module-info.java
,而无需键入export
命令。
NetBeans RCP 模块API (NetBeans胖客户端平台随附的API)中已经存在此功能。
在由许多模块组成的项目中,通常很难找到依赖项存在于哪个模块中。 一个很好的补充就是能够在整个模块(和/或库模块)中搜索我们要查找的类。 NetBeans RCP 模块API (NetBeans胖客户端平台随附的API)中已经存在类似的功能。
当由于NetBeans找不到依赖项而遇到错误时,单击提示blob将打开一个对话框,开发人员可以在其中键入所需的类,如下图所示,然后选择适当的模块。 当开发人员请求将模块依赖项添加到模块路径时(例如,通过右键单击Libraries ),可以访问相同的对话框。
最后,可以将一个新模块添加到Java Modular Project中,但是无法删除模块,尽管至少要编写这些行。 这是一个固定的EA错误。
NetBeans 9 EA仍在大量开发中,并且尚未通过官方测试(也称为NetCat),因此在此阶段会遇到一些错误或奇怪的行为是正常的。
六,结论
在本文中,我们了解了NetBeans 9 EA如何支持JDK 9 EA并简化了开发人员的工作。 作为开发人员,您无需记住如何使用模块路径来构建和执行Java模块的细节,或者如何jlink
命令的细节。 NetBeans 9隐藏细节。 JShell也很好地集成了。 当然,某些改进可以使开发人员的生活更加轻松,但是这些改进将在将来的NetBeans版本中或作为插件出现。
我们看到了可以用来创建模块化Java应用程序的两种项目。 我们看到了可以在module-info.java
内部使用的五个可用命令中的四个命令的使用: exports, requires, uses
和provides
。 opens
允许其他模块使用反射来访问您打开的包中的类型。
普通模块中的特定程序包可以被“打开”,因此只有该程序包可在运行时进行深度反射:
com.greetings.module-info.java
module com.greetings {opens com.greetings;
}
一些Java框架和工具在运行时严重依赖反射来访问未导出模块的代码。 它们提供诸如依赖项注入,序列化,Java Persistence API的实现,代码自动化,调试等功能。示例包括Spring和Hibernate。 这些框架和库不了解您的应用程序模块,但是它们需要访问模块的类型和私有成员,这打破了JDK 9中强封装的前提。还可以打开整个模块以进行反射,例如:
com.greetings.module-info.java
open module com.greetings {requires com.socket;
}
与opens
与exports
进行比较, exports
语句可让您在编译时和运行时仅访问指定包的公共API,而opens
语句可让您在运行时使用反射来访问指定包中所有类型的公共和私有成员。
与拼图游戏配合使用时,玫瑰花还不是全部。 专家社区还没有接受拼图游戏,并且对他们遇到的许多严重缺陷有很多担忧。
7.参考
- NetBeans 9 EA
- NetBeans 9 EA JDK 9支持
- Sitepoint Java 9终极指南
- JDK 9功能完整 ,JavaCodeGeeks
- Java杂志 ,2017年7月至8月
- Java 9系列: JShell ,Voxxed
- Java 9 系列 :HTTP / 2 客户端 ,Voxxed
- Java 9系列: JVM ,Voxxed
- Java 9系列:HTML5和 Javadoc ,Voxxed
- Java 9 系列 : 并发 更新 ,Voxxed
- Java 9系列:变量 句柄 ,Voxxed
- Java 9系列:封装大多数内部 API ,Voxxed
- Java 9系列:多发行JAR 文件 ,Voxxed
- Java 9 系列 : 分段 代码 缓存 ,Voxxed
- Java 9系列: 集合的 便利工厂方法 ,Voxxed
- 拼图中的严重缺陷
- Bateman A.(2016),“为JDK 9做准备”, JavaOne 。
- Bateman A.(2016年),“模块化开发简介”, JavaOne 。
- Bateman A.和Buckley A.(2016),“高级模块化开发”, JavaOne 。
- Buckley A.(2016),“模块和服务”, JavaOne 。
- Buckley A.(2016年),“ Project Jigsaw:The Hood”, JavaOne 。
- Bateman A.,Chung M.,Reinhold M.(2016年),“ Project Jigsaw Hack Session”, JavaOne 。
- Deitel P.和Deitel H.(2017年),《 Java 9 for Programmers》,第四版,Deitel。
- Evans,B.(2016),“ Java 9模块的早期观察” ,《 Java Magazine》 ,第26期,1-2月,第59-64页。
- Gupta A.(2015年),《 JDK 9 REPL:入门》 ,JavaCodeGeeks
- 慢跑TM。 (2016), 学习模块化Java编程 ,Packt。
- Mak S.和Bakker P.(2016年), Java 9 Modularity, O'Reilly(早期发行)
- Reinhold M.(2016), 问题模块反射访问 , Voxxed
- Sharan K.(2017年),《 Java 9公开供早期采用和迁移》 ,Apress。
- Verhas P.(2017年),《 Java 9示例编程》 ,Packt。
- Zhitnitsky A.(2015), Java 9抢先体验:与JShell进行动手实践– Java REPL ,JavaCodeGeeks。
翻译自: https://www.javacodegeeks.com/2017/07/netbeans-9-early-access.html