JDK 10 的新特性和增强功能

JEP 286 局部变量推断: var

传统的 Java 代码中,声明一个变量是非常繁琐的:

List list = new ArrayList();

现在则引入了 var,既保持 Java 对静态类型安全的承诺,又能让开发者省略不必要的局部变量类型的声明。

比如像这样:

var list = new ArrayList();

var 只能用在以下情况:

  • 声明的同时赋值,比如上面那个例子
  • 增强的 for 循环中的索引 (这将在后面提到)
  • 传统 for 循环中声明的本地变量

它不能被用于方法签名、构造器声明、方法返回类型、字段、异常捕获或任何其他类型的变量声明。

类型推断在 Java 8 中已经得到了显著的扩展,包括了对嵌套和链接的泛型方法的推导以及 lambda 表达式的推导,比如下面这样:

int maxWeight = blocks.stream()
                    .filter(b -> b.getColor() == BLUE)
                    .mapToInt(Block::getWeight)
                    .max();

没有必要去在意 blocks.stream*() 返回的是 Stream,也不用在意 filter 中 lambda 表达式的参数类型没有被显式声明,因为它们都能通过类型推导得到。

对于局部类型变量,类型推导是非常有用的,因为通常情况下都可以写成下面这种代码:

var path = Paths.get(fileName);
var bytes = Files.readAllBytes(path);

但是要注意的是,var 并不是一个关键字,而是一个保留的类型名称,这意味着你可以将一个变量、方法、包名写成 var。不过一般情况下不会有人这么写的,因为这本身就违反了普遍的命名规范。

var 不能用来声明没有赋值的变量、不能用于声明多个变量的情况、不能声明具有额外纬度的数组或引用了正在初始化的其他变量的变量。

JEP 322 基于时间的版本号管理

JEP 223 引入的版本号方案相比之前的方法都要好很多,但是对于现在 Java 所走的 六个月节奏 并不适合。

JEP 223 的问题在于,版本号中编码了它和它对之前版本的兼容性信息。但是在 六个月节奏 的情况下,这些信息都是未知的,在发布前任何事情都可能发生,由此 JEP 223 规范下的版本号也会是未知的。

在 JEP 223 的语义中,每个基于 JDK 构建或使用组件的开发者(包括 JDK 的发布者)都必须提前敲定版本号,然后切换过去。而库、框架和工具的开发者则必须在代码中修改检查版本号的相关代码,这造成了混乱。

更多关于 JEP 322 的详细情况请访问这里。

core-libs/java.util Optional.orElseThrow() 方法

此次更新中为 Optional 类添加了一个新的方法,这个方法与 orElseThrow(exSupplier) 是不同的,它没有参数。

Optional.get() 是一个容易误导程序员的方法,它实际上有可能抛出 NoSuchElementException 运行时异常,因此需要一个语义明确的方法来帮助程序员清楚的认识到自己在做什么。

在之前的规划中,Optional.getWhenPresent() 是一个备选方法,虽然这个名字强调了值一定会存在,但是其中的 when 似乎会让人觉得这是个阻塞方法。因此最后委员会选择了 Optional.orElseThrow(),但是 Optional.get() 并没有被单纯得弃用。

Unicode 标签扩展

增强了 java.util.Locale 和相关 API 以实现关于 BCP 47 语言标签 的 Unicode 扩展。

详情请访问这里

core-libs/java.util 创建不可变集合的一系列 API

新的 API 中包括了一些用于创建不可见集合的方法:

List.copyOfSet.copyOf 和 Map.copyOf() 可以用来从已有的集合中创建一个新的集合;

toUnmodifiableListtoUnmodifiableSet 和 toUnmodifiableMap 则存在于 Stream.Collectors 类中,通过它们可以将流的元素收集到一个不可变集合当中。

copyOf 系列的方法签名基本是下面这样:

static  List  copyOf (Collection  coll)

Set 和 Map 也会有类似的方法。

值得注意的是,这个方法会检测 coll 是不是一个 不可变 的集合,如果是,则会直接返回该引用,相当于一个浅拷贝。

另外对于不可变集合而言,下面这种情况是被允许的:

List  list = List.of(...);
List  newList = List.copyOf(list);

这对于可变集合而言是不允许的,因为这可能会导致堆污染。

core-svc/java.lang.management 用于关闭 JRE Last Usage Tracking 的系统属性

引入了新的 jdk.disableLastUsageTracking 属性以禁用 JVM 的 JRE 上次使用情况追踪 功能。

如果使用了此属性,那么 com.oracle.usagetracker.track.last.usage 的设置将被忽略。

core-svc/java.lang.management 开箱即用的 JMX 代理使用的散列密码

在以前,JMX 存储的是明文密码,当时 (2014 年) 大家已经开始手动生成散列密码来替换明文密码,因为这可以避免一些攻击,同时盐 (salt) 的存在可以让散列密码的强度更上一层。但是对于管理员而言,即使是使用诸如 Python 之类的脚本语言来生成散列密码,这仍然是极容易出错的。因此最好的办法是用户只需要提供明文密码,让 JMX 来处理散列之类的事情。

JMX 现在会使用密码的 SHA3-512 散列结果来覆盖存储在 jmxremote.password 中的明文密码。

其格式如下:

role_name W hashedPassword

其中:

  • role_name 是任何不含空格或制表符的字符串
  • W 是一个空格或制表符

散列密码的格式如下:

hashedPassword = base64_encoded_64_byte_salt W base64_encoded_hash W hash_algorithm

其中:

  • base64_encoded_64_byte_salt 是 64字节的随机值
  • base64_encoded_hash 是 Hash_algorithm(password + salt) 的结果
  • W 是一个空格或制表符
  • hash_algorithm 是列表 中指定的算法名称。这是个可选项,默认值为 SHA3-512

如果密码是明文的,且满足以下条件,那么将被散列值覆盖

  1. management.properties 文件中 com.sun.management.jmxremote.password.toHashes 属性被设为 true
  2. 密码文件是可写的
  3. 安全管理器处于打开状态的情况下系统安全策略允许对密码文件进行写入

如果想更改角色的密码,可以将旧的散列密码替换为新的明文密码或新的散列密码。如果新的密码是明文的,那么在新的登录发生时系统会使用其散列值替换明文密码。

文件中的角色应该至少有一条记录,否则该角色将无权访问;如果同一个用户下有多条记录,那么会使用最后一条记录。

用户可以自行根据上述的格式来生成的散列值,以替换旧的密码。

文件被拥有者以外的用户访问时将导致错误并退出程序。

为了防止在生产环境中对密码文件进行了无意的编辑,建议只部署可读的散列密码文件。明文密码的散列密码列表可以用 JMX 代理预先生成。

在 JMX 运行期间,建议不要编辑密码文件。程序会对文件的完整性做一个保护,因此修改既可能丢失。

hotspot/gc JEP 307 G1 的 完全并行 GC

G1 垃圾收集器就是为了避免 完全 GC (full collections) , 但是当并行收集无法快速回收内存时,会产生一次 完全回退 GC(fall back full GC) 。

之前 G1 的 完全GC 使用的是单线程标记扫描压缩算法(mark-sweep-compact),现在通过 JEP 307完全GC 得以并行化,同时现在会使用与 年轻代 、混合收集 相同的并行工作线程数量。

security-libs/java.security JEP 319 根证书

OpenJDK 中的 cacerts 密钥库在相当长一段时间内是空的,这将导致未指定 javax.net.ssl.trustStore 属性的情况下 TLS 连接 的创建会被阻止。现在Oracle 的 Java SE 根证书 被填充至 OpenJDK 的 cacerts 中。

security-libs/javax.net.ssl TLS 会话散列 和 主密钥扩展 的支持

在是一个对 RFC 7627 的支持。

如果出现兼容问题,可以将 jdk.tls.useExtendedMasterSecret 设置为 false 来禁用此扩展的协商。

如果 jdk.tls.allowLegacyResumption 值为 false,当会话散列和主密钥扩展未协商的情况下,程序可以拒绝简短握手。

如果 jdk.tls.allowLegacyMasterSecret 值为 false,应用将拒绝不支持此扩展的连接。

tools/javac 增强 for 循环的字节码生成

之前的 for 循环在遍历一个大的列表/数组时,它会隐式得持有一个临时引用,而且这个临时引用不会被释放以至于可能产生 OutOfMemoryError 异常,详细的情况可以查看这里。

对于下面的 Java 代码:

List data = new ArrayList<>();
for (String b : data);

将会被编译为:

{
    /*synthetic*/
    Iterator i$ = data.iterator();
    for (; i$.hasNext(); ) {
        String b = (String) i$.next();
    }
    b = null;
    i$ = null;
}

这意味着 GC 可以发现不再被使用的 i$ 所占用的内存并回收它,这对于数组也是适用的。

tools/javadoc(tool) javadoc 支持多个样式表

新的 javadoc 命令行选项 --add-stylesheet ,它支持在生产的文档中使用多个样式表。

而现有的选项 -stylesheetfile 有了一个别名 stylesheetfile --main-stylesheet,用以区分主样式表和其他样式表,更多信息可以查看 javadoc 文档。

tools/javadoc(tool) 覆写没有改变规范的方法

默认情况下,如果有方法覆写了超类中的方法,那么 javadoc 会为它生成文档。

但是如果一个方法只是被覆写但是没有改变语义行为时,javadoc 只会在原方法摘要中用 @inheritdoc 标记它。

现在新的选项 --overridden-methods=value 可以将许多 不改变规范的覆写方法 与其他继承而来的方法分组,而不是在类的声明中与其他方法一起被记录在文档中。

tools/javadoc(tool) API 描述中摘要的注释标记

{@summary ...} 是一个新的行内标记。默认情况下,会通过简单算法或 java.text.BreakIterator 对描述中的第一句话进行判断以确定 API 描述内容的摘要,但是这种方法可能会对第一句话结尾有错误的判断,因此新的标签可以显式得指定 API的描述摘要。

你可能感兴趣的:(JVM)