五年过去了,再看 Java 缺失的特性

假如想象 提交于 2020-04-23 07:22:36

本文要点:

  • 在过去的五年中,Java 语言发生了显著的变化
  • 正在实现这一变化的有两个主要的项目:Valhalla 和 Amber,它们仍在进行中
  • Java 继续保持其向后兼容的核心价值
  • 尽管已经 25 岁了,但 Java 在语言和平台上仍然具有很强的生命力
  • 诸如 Graal 之类的新技术正在帮助 Java 继续保持在编程语言的前沿上

大约是五年前,我写了一篇文章,概述了其他语言的一些特性思想,我认为这些思想可能会对 Java 有好处。从那以后发生了很多事情:那时,Java 8 是最新的发布版本,而现在,最新的版本是 Java 14。

让我们依次查看下每个特性,看看它的当前状态是什么:它是已经被添加到 Java 中了,还是正在开发中,亦或是当前尚没有将其纳入 Java 的计划。

具体化泛型

我最初的预测排除了具体化泛型(reified generics)。我没有预见到 Valhalla 项目对从头开始重构 JVM 的雄心壮志。

Valhalla 项目的主要目标是:

  • 使 JVM 内存布局行为与现代硬件的成本模型保持一致;
  • 扩展泛型,以允许对所有类型(包括原语、值、甚至 void)进行抽象;以及
  • 使现有的库(尤其是 JDK)能够兼容地演变,以充分利用这些特性。

在此描述中隐藏的是已加载的单词“values”,它已经演变成我们今天称之为内联类的特性。

因此,具体化泛型和原语专业化已经被包含到一个更大的项目中了,该项目承诺从根本上改变 Java 的编写和执行方式。

尽管正在进行深层次的底层变更,但项目团队的目标包括最大程度地减少对现有 Java 应用程序的破坏,并为在自己的代码中使用 Valhalla 功能的开发人员提供一种简单的、可选的方法。

我们应该注意到,Valhalla 在很大程度上仍然是一项正在进行的项目,而且尚无关于其何时交付的正式路线图。

判决:正在进行中(作为 Valhalla 项目的一部分)

无符号算术

在 Java 的历史上已经反复讨论过支持该特性的可能性了,但是引入该特性的过程涉及到了许多复杂性。

例如,有这样一个问题,即在 Java 类型系统中如何表示有符号性,以及它是否应该在 JVM 字节码级别上可见。

这些问题尚未达成任何令人满意的共识,因此,Java 仍然不包含无符号算术,而 Valhalla 项目的一个显著方面是:它不包含对无符号算术的支持。

判决:尚不予考虑

数组 long 型索引

Java 数组的大小受到其设计的一个简单事实的限制:它们是以 int 作为索引的。这意味着一个数组被限制为 2^31 个元素(记住 int 是有符号的),即大约 20 亿个条目。

正如最初所设想的那样,使用 long 而不是 int 的想法将能允许开发人员创建和操纵更大的数组。然而,自从发表最初的“缺失特性”一文以来,社区在此领域的关注点已经转移到提供对堆外存储的大型数组的便捷访问上了。

造成这种情况的原因有很多。易于与非 Java 库(包括机器学习和其他大量的应用程序)进行互操作是一个重要原因。然而,大型的堆数组到底有多大用处,这也是一个问题。在进出 Java 堆时,庞大的、具有多个千兆位的数组将会产生巨大的复制成本,并可能会给 JVM 的垃圾收集器造成严重的困扰。

由于这些原因,在堆外支持的背景下,才会考虑大型数组,并且该概念已被纳入 Panama 项目中了,该特性正在积极地开发中。

判决:正在进行中(作为 Panama 项目的一部分)

更具表现力的导入语法

即使在局部(或文件作用域)级别,也没有认真尝试扩展导入语法的作用域或引入类型别名。

判决:尚不予考虑

集合字面量

在 Java 9 中添加了接口上的静态方法,并且对集合进行了更新,以包括用于集合的工厂方法。它们在 Java 中扮演集合字面量(Collection Literals)的角色:

1var ls = List.of(1,2,3);

由于工厂扮演字面量的角色时,此变更还引入了集合接口的新实现。这些实现是不可变的,因为重用现有的可变集合(例如 ArrayList)将违反程序员的期望,即这些值应该表现得像字面量一样。

更具侵入性的解决方案,例如将新字面量直接引入到语言语法中,则不予采用。

判决:已交付(作为工厂方法)

代数数据类型

Java 中的代数数据类型(Algebraic Data Type)正在交付中。该特性包括对类型系统的两个主要补充:记录(Records)和密封类型(Sealed Types)以及模式匹配,这是一种非常重要的新语法。

Java 14 提供了这两个方面的预览版,具体来说是:记录(Records),在 Java 中本质上是命名元组;以及模式匹配的初始组件。

组成模式匹配介绍部分的新特性是 Java 的首个模式形式: instanceof 模式 switch 表达式的标准化版本。

第二个是脚手架(Scaffolding),它将最终支持通用模式匹配的引入,可能会以类似 Scala 程序员所熟悉的匹配表达式的方式引入。

在该特性完全交付之前,还有许多步骤需要执行:记录和模式仍然只是处于预览状态。因此,需要进一步的 JEP(包括 JEP 375 ,它扩展了 instanceof 模式来析构记录)来充实整个模式匹配。

随着 Java 14 的到来,关键的 JEP(包括 JEP 375 和 JEP 360 ,它们引入了密封类型)并不是任何特定 Java 版本的目标。

尽管缺乏具体的路线图,但整个代数数据类型和模式匹配机制很有可能可以在下一个 LTS 版本(即 2021 年 9 月的 Java 17)中以标准化的形式及时交付。

判决:正在进行中(作为 Amber 项目的一部分)

结构类型

自 Java 8 以来,Java 的类型系统已经有了一定程度的发展,但是在实践中并没有朝着通用的结构类型的方向发展。例如,在设计记录时,为了使记录成为命名类型,显式地拒绝了结构类型。

这就强化了这样一个观念,即我们赋予类型的名称具有强大的力量和重要性,Java 记录不仅仅是由其组件的数量和类型来定义的。

在 Java 中,类似于结构类型的东西仍然隐约可见的一个小地方是在 Java 的不可表示类型中。实际上,这些只是最初在 2015 年文章中讨论的示例的一个扩展。

在这个例子中,我们构造了一个看起来像结构类型的对象(Object + ),但只能在单个表达式中使用它,因为没有可表示的类型能够用作可赋值的变量类型。

从 Java 10 开始,该语言就已经具有类型推断的扩展形式了,类型推断使用 var 来减少赋值中的样板。我们能够使用此特性来 http://extend the scope">扩展作用域,在作用域内,我们可以调用以这种方式定义在类型上的其他方法。但是,这仅限于发生类型推断的方法。 var 推断的特殊类型不能精确地跨越方法的边界,因为它是不可表示的。

实际上,这些特殊案例并不是真正的结构类型,也没有引入它的意图。 Java 的设计对名称和命名类型的吸引力太强了。

判决:考虑过但驳回了

动态调用点

在过去的五年中,对 invokedynamic 的使用已经有了很大的扩展,尽管只是在 JDK 和少量技术成熟的外部库中。

正如原文所述,“Java 语言没有关键字或其他结构来创建通用的 invokedynamic 调用点”。

关于可以扩展 Dynalink 库来承担这个角色的建议从未被采纳过,实际上,产生 Dynalink 的 Nashorn Javascript 实现本身现在也已经被弃用了,可以从 Java 15 中删除(尽管 Dynalink 本身将保留)。

那些真正使用动态调用点的库通过 MethodHandles API 来实现的,尽管该方法在 2020 年使用起来会稍微容易一些,但是对大多数 Java 程序员来说,它仍然是遥不可及的。

在不引发太多运行时问题的灵活动态调用和引人注目的语言级别使用之间,寻求平衡的困难已经被证明了,该困难至少在目前是非常巨大的。

判决:尚不予考虑

我错过了什么?

在过去的 5 年里,也出现了一些项目和趋势,这些项目和趋势我并没有在最初的文章中预测或解决。其中最重要的可能是:

  • Valhalla 项目的程度和范围
  • Amber 项目
  • Java 最新的发布模型
  • Graal 和 GraalVM
  • Kotlin 的崛起

举几个例子:

尽管 Valhalla 项目是于 2014 年启动的,但在随后的几年里,它势头强劲并取得了巨大的发展。它已经成为 Java 迄今为止最雄心勃勃、最大的变化了。 Valhalla 承诺将会修改 Java 平台的各个方面:从内存和值在 VM 中的表示方式,到类型系统和泛型,再到库级和语言级语法。

该项目旨在使 Java 与当前和未来的硬件状态保持一致,并提供性能和其他一些根本无法单独解决的改进。相反,Valhalla 的目标是将 Java 平台的状态从当前位置(我们可以认为是局部最大值)转移到一个更适合作为未来几十年平台基础的位置。

最初的研究总是很难预测的,因此 Graal 的兴起也让我感到惊讶,但也不足为奇。与其他许多引人入胜的概念一样,一旦你掌握了它,它的基本概念就会变得非常简单。

Java 常用的 JIT 编译器是用 C++ 代码编写的,并在 JVM 特殊的专用线程上执行。 Graal 从一个简单的想法开始:如果 JIT 编译器是用 Java 编写的,而实际上编译器线程正在运行的是 Java 解释器的第二个副本,那该怎么办?

解释模式的 JIT 编译器将具有与当前 JIT 相同的特性。因此它可以将任何类文件编译为机器码。我们应该预期它会比现有的 C++ 编译器慢,但它的行为并不会有所不同,只是在性能上不同。

将此想法归结为逻辑结论,这意味着解释模式 JIT 可以编译构成 JIT 本身的类文件。一旦预热,它就可以替换自己,并以与原始的原生 JIT 相同的性能运行。

事实证明,这个引人入胜的想法是一大类新技术的起点,其中包括 Java 的本地编译(AOT)以及能够运行多种不同语言的全新的多语言虚拟机(GraalVM)。

结论

在过去的五年中,Java 平台已经变得越来越复杂了,有许多新的语言和虚拟机(VM)特性已经交付或正在开发。假设按照当前的趋势继续下去,社区最感兴趣的可能是在 Java 17(将于 2021 年 9 月发布)中提供的一组标准化的特性。

这将是一个截然不同的 Java, 与我们在 2014 年最初观察时的 Java 不同,虽然某些特性已经交付了,但似乎很明显,其他一些特性不太可能实现了,还有一些特性是通过完全不同的形式实现的。我们期待着看到未来五年 Java 语言和平台的发展,尤其是那些我们目前尚无法预测的方面。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!