Java20周年发展史

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(77)   2024-05-27 16:20:14

二十年前,在苏黎世的一间公寓里发生了两件大事。

我的女儿迈出了她的第一步,一位年轻的博士后研究员(她的父亲)迈出了他使用 Java 的第一步。真的很难完全理解当时的 Java 是什么。在这些日子里,TCL 风靡一时,而 Java 与冰箱和烤面包机的关系有些奇怪。 Java 没有明显的用途,但不知何故,它像蒸汽火车一样在陡峭的斜坡上获得动力。

最初吸引我使用该语言的实际上是 applet。将分子结构的实时 3D 流涎嵌入这些“新的和风靡一时的”网页之一的想法似乎非常令人陶醉。与此同时,对于 Fortran 和 C 程序员来说,Java 似乎是一种难以想象的笨拙和不优雅的语言。

在接下来的 20 年里,我离开 Java 的时间从未超过几个月。它改变了计算世界,并在一定程度上打破了微软在其鼎盛时期非常热衷的 IT 垄断。 Java 变得更加强大,速度快得难以想象,可扩展性无限提高,而且明显更加笨重,同时,它的数量少得可怕,但优雅得多(可变句柄、自动装箱——阴阳)。

在这篇文章中,我希望对 Java 在过去 20 年中的发展进行非常个人化的回顾,突出一些好的和一些坏的,以及一些非常丑陋的东西。这将充满感情地完成,并希望能阐明 Java 的发展方向以及它面临的危险。我将未来学留给下一篇文章。

Java 有多重要?

让我们不要对此感到娇气; Java 是有史以来仅有的 4 种真正改变范式的商业相关通用编程语言之一。 Fortran、COBOL、C 和 Java。我们都可以拥有自己最喜欢的语言,并滔滔不绝地说 Python 在计算史上比 COBOL 更重要,或者说 C# 比 Java 更好,甚至更重要。然而,Python 和 C# 都没有改变任何范式(C# 一直都是对 Java 的增量重新构想,而 Python 实际上是 awk 的一个遥远的后代)。 SQL 不是一种通用语言,而且 Lisp 从来没有商业相关性(招来仇恨者——但确实如此)。

C++ 的旁白解释了为什么它不在我的列表中:简而言之,在 Java 出现之前,C++ 并不是一个足够大的因素。人们并没有从 COBOL 转向 C++。虽然它是一种重要的语言,但其范式转换世界观改变影响远不及 Java。

Java 与神秘博士的相似之处

Java 并不是持续成功的动力源泉,但它确实是成功的动力源泉;我们可能愿意相信它的进展是有重点和有计划的,同时对一些主流 Java 开发的彻底失败和“声音关闭”带来的惊人成功视而不见。

每次 Java 和 JVM 似乎都处于被某些克星(C#、Go、Ruby 等)歼灭的边缘时,就会发生再生,从而产生另一系列激动人心的事件。即使是可怕的伤口,如 JNI 接口或可怕的并行执行器流混乱之类的创伤,都不足以杀死我们的英雄。同样,Java 7、8 和 9 中引入的热点 VM 和大量编译器优化技巧等显着的性能增强,在 CPU 时钟速度停滞且崩溃后 IT 预算渴望节省成本的世界中不断保持 Java 的相关性.

逃逸分析对 Java 逃逸成本分析有帮助吗? (好吧,亚历克斯,那个太多了,退后一步。)

尽管回顾的自然趋势是跟随时间的箭头,但我发现在对 Java 进行回顾时面临着巨大的挑战。与其他最具商业重要性的语言 C、Fortran 和 COBOL 一起,Java 的历史与其运行时一样是多线程的,并且随着外力对 Java 的影响而递归,而 Java 也同样重塑了 IT 世界。

为了说明这一点,我们可以看看 JEE 和 Hadoop。

大象与鱼

在世纪之交,编程变得有点疯狂。一些本来应该非常简单的东西,比如提供网页,突然需要(感觉像)XML 页面和 Java 代码来定义一个“Servlet”。该 servlet 将在“应用程序服务器”中得到进一步支持,该“应用程序服务器”具有更多定义 Java bean 的 XML,这些 Java bean 游弋在配置和服务的海洋中。

有些读者可能会觉得我的个人观点令人反感,并认为 J2EE(现在改名为 JEE)曾经/非常出色。这在某些方面是因为它展示了一种新的现代编程语言如何最终打破大型机对商业规模业务计算的束缚。像 JDBC 和 JMS 这样定义明确的 J2EE 部分(或它使用的部分)真的很棒。突然之间,我们拥有了良好的业务处理工具,例如数据库连接和系统间消息传递。 Java 看起来真的可以将从银行业到仓库管理的一切重塑为分布式计算环境。

问题是 Java Enterprise Edition 的实现几乎在所有方面都很糟糕。我是从个人经验而不是从理论的角度这么说的。早在 2000 年代初期,我就是一名 J2EE 开发人员。

故事是这样的:“一切都太慢了。结束。”。

为了更加亲切,我会提供更多细节。我在一家为零售业开发软件的公司工作。他们的解决方案最初都是用 C 语言编写的,并与 Oracle 关系数据库一起使用。转向 J2EE 对他们来说是一个巨大的赌注,需要在再培训和其他资源方面进行大量投资(他们破产了)。这种新的基于 Java 的软件系列的客户之一是一家新兴的(多年后仍在运营)互联网杂货店。他们的系统由大型(按当时的标准)16 个 CPU Sun 服务器组成。

J2EE 系统的开销及其笨拙的状态管理,其中一些 bean 通过 JDBC 将数据持久保存到数据库,而其他管理逻辑等则扼杀了性能。即使在后来的 J2EE 版本中出现了“本地”和“远程”接口的想法,严重依赖 JNDI 来查找 bean,然后序列化以在它们之间进行通信,这仍然是一种削弱。

该系统进一步依赖于 JMS,这在当时的 Weblogic 中是灾难性的(如果我没记错的话,版本 5)。实际上,我们开始使用的 Weblogic JMS 实现使用 Oracle 8i 无法在事务内部管理的 blob 类型将消息序列化到 Oracle。是的,JMS 消息持久性是非事务性的,但他们仍然要钱来解决这些垃圾问题。

因此,我花了 6 个月的时间从 J2EE 中提取业务逻辑代码,并在我们现在称为 POJOS(普通 Java 对象)的地方实现它们。我更进一步,将 JMS 替换为基于 PL/SQL 的消息传递系统,该系统可使用 PL/SQL 到 Java 绑定从 Java 访问。所有这些都运行良好,而且比 J2EE 系统快很多很多倍。

然后我的一个朋友和同事用 PL/SQL 重写了整个东西,而且速度更快。

从那时起,这毒害了我对 J2EE 的看法,您可能不会感到惊讶。它的基本失败是对极其复杂和缓慢的抽象以及应用服务器概念的痴迷。这些实际上都不是必需的。

就在 JEE 的压倒性重量似乎预示着大规模商业 Java 的长期缓慢死亡时,谷歌以其关于 GFS、Map-Reduce 和 BigTable 的著名论文震惊了世界。谷歌文件系统和运行在它之上的系统开创了一种新的处理思维方式。运行服务器然后运行进程的计算机的“具体化”编程模型消失了。此外,整个方法有点低级概念;在计算资源的大冗余“云”中运行简单的东西。然而,与 JEE 的紧密接口和抽象世界相比,这些“东西”的规定要少得多。

我们没有屈服于这个新的克星,而是让 Java 重生为一个全新的野兽。 Hadoop 诞生了,而不是云成为 Java 在企业中的死亡,它在可预见的未来将 Java 嵌入到该企业中。

电话是新冰箱

将平台独立性引入开发人员意识是一件事,我相信我们都对 Java 深表感谢。将软件开发视为很大程度上独立于操作系统供应商的炒作彻底改变了更高层次的系统架构思维。那个可以在 Windows 上编写一些东西并在 Linux(或 Solaris 或 Irix 或其他任何东西)上运行的人在 90 年代后期只是头脑融化。

我个人认为,Java 的平台独立性和 Hadoop 的坚固简单性的结合是阻止微软通过 .Net “接管世界”的两大力量。

这种平台独立性从何而来?当时它的根本目的是什么?好吧,我们可以改写历史并在事后说不同的话。尽管如此,我清楚地记得孙说这一切都与冰箱和烤面包机有关。不知何故,他们完全相信自动化设备是未来(正确)并且 Java 将是编写一个设备管理程序并在任何地方运行它的方式(错误)。

把第二部分弄错算不上什么大失败; Sun 不可能预料到运行稳定的开源操作系统的超低成本 CPU 会被证明是虚拟机之上的抽象选择。 Linux 通过在操作系统级别提供平台独立性和免费性彻底颠覆了世界。然而,那是另一个故事,而不是 Java 的故事。取而代之的是 Android。

许多商业 Java 开发人员并没有真正考虑 Android 的影响,因为它不运行 JVM。尽管如此,它确实运行 Java。现在情况发生了一些变化(据我所知),但甚至在 5 或 6 年前,开发 Android 应用程序的标准方法是使用 Android 模拟器在 PC 上用 Java 编写它,将其编译成字节码,然后将 JVM 字节码交叉翻译成 Dalvik 字节码。

事实上,这个过程非常可行,以至于当我使用 Microfocus 时,我们将 COBOL 编译为 JVM 字节代码,然后将其转换为 Dalvik,然后在 Android 手机上运行 COBOL 应用程序。我并不是说这是一件好事,但这确实很有趣。

我的观点是,Android(以及之前的 Java 功能手机)使 Java 与一个由新兴开发人员组成的庞大社区相关联。我怀疑大学现在教授 Java 而不是 C# 是因为 Android。再一次,“Voices off”拯救了 Java 并让它重新生成一个新的 Doctor,在一个伟大而激动人心的新系列 中接受新的挑战(实际上 - 我不看 Dr Who - 我在 70 年代看过和 80 年代;当 Lalla Ward 和 Tom Baker 离开该系列时,我有点失去兴趣)

回顾关于“Android 是否是合适的 Java”的讨论以及 Google 和 Oracle 之间的一些敌对情绪,我有些苦笑;毫无疑问,谷歌采用 Dalvik 和 Java 作为 Android 平台极大地提高了甲骨文拥有的 Java 资产的价值。

简约优雅 – JMM

Java 很少被视为开创性的简单和优雅,但在某一方面它确实为其他主流语言指明了前进的方向。作为 Java 5 标准的一部分引入新的 Java 内存模型是简单性和有效性的胜利。

让我们认真考虑一下这有多大;大型商业编程语言之一首次以明确的术语阐述了多线程环境中该语言的所有“先发生”关系。对边缘案例的所有担忧都一去不复返了;由于试图保持最初从未指定的行为之间的相似性,所有缺失的优化。突然间,Java 成为开发无锁和无等待算法的“首选语言”。关于跳跃列表实现之类的学术论文可以基于 Java。此外,该模型随后渗透到基于 JVM 的任何其他语言。

其他 JVM 语言不是它影响的极限;引用维基百科:

“Java 内存模型是为一种流行的编程语言提供综合内存模型的首次尝试。[5]并发编程的日益流行证明了这一点和并行系统,以及为此类系统提供具有清晰语义的工具和技术的需求。从那时起,对内存模型的需求得到了更广泛的接受,为 C++ 等语言提供了类似的语义。[6]

所以,是的,Java 教会了 C++ 如何进行内存建模,我感受到了 Java 5 和 C++ 11 的影响。

不安全但需要任何速度

Java 的致命缺陷,自从 hotspot 最终将编译/解释搁置一旁以来,一直是并且很可能永远是它的资源分配模型。 Java(与许多其他语言一样——例如 Python)将内存视为与其他任何事物完全不同的资源。考虑 C,其中内存是通过 malloc 分配的,它返回指向该内存的指针;通过调用 free 来释放此资源。 C中的文件一般用fopen打开,fclose关闭。也就是说,C中内存和文件资源的使用是对称的。 C++ 在基于范围的资源管理(RAII——甚至 Stroustrup 承认这是一个糟糕的名字)方面走得更远,它允许以相同的方式对称处理内存资源(新建/删除)和其他资源(文件、套接字、数据库连接等)并且通常是完全自动的。

出于某种我不清楚的原因,在 90 年代开发将内存资源与所有其他资源完全不同的编程语言被认为是一个好主意。从 CPU 的角度来看,这并没有多大意义。主内存通过芯片组连接到 CPU,硬盘驱动器和网卡也是如此。为什么记忆在某种程度上与其他两个有很大不同?

事实上,我们在过去 20 年中看到的是主内存变得越来越像所有其他资源,因为与 CPU 速度相比,内存延迟已成为一个越来越大的问题。在现代 NUMA 架构中,跨越主板到达单独的内存条可能需要数十个时钟周期。此外,内存不足比其他资源问题更致命。例如,内存比网络连接更宝贵。如果套接字被丢弃,程序可以尝试在循环中重新建立它;如果发生内存不足错误,则程序注定失败。事实上,它甚至可能无法记录错误的发生。

除了资源管理的不对称性,Java 在 IPC 和内部线程间通信方面也很差(现在不那么重要了——见后面)。你现在可能对着屏幕大喊“但是 Java 对线程间通信和 IPC 处理套接字有很好的库支持”。虽然这是事实,但世界仍在继续前进;遭受上下文切换以将数据从一个线程传递到另一个线程或从一个进程传递到另一个线程不再是可以接受的。基于内存栅栏的队列和共享内存的广泛采用开始使 Java 相对于 C 和 C++ 显得笨重和缓慢。尤其是随着 C++11 的采用,Java 的能力看起来很可怕。

但是,通常情况下,社区找到了解决方法。潜伏在 JDK 内部的是(仍然有待明确)这个名为 sun.misc.unsafe 的类。在 Java 8 中,它甚至得到了实质性的改进和扩展。事实证明,JDK 开发人员需要比提供的公共 JDK 类更底层的计算机硬件访问权限,因此他们不断向这个黑暗的秘密添加内容。

当我在摩根士丹利工作时,我参与了一个项目,让 C++ 低延迟系统通过共享内存与 Java 进行“对话”。为了确保 Intel x86 上的原子方法与 C++11 标准和 sun.misc.unsafe 相同,我查看了开放的 JDK 本机代码。事实上,虽然一些 sun.misc.unsafe 操作有点次优(例如循环 CAS 进行原子写入而不是使用锁前缀移动),但 fence on write 和 rely in ordered reads matched 1 的方法: 1 使用 C++11。

因为 sun.misc.unsafe 方法是固有的,所以它们的性能非常好,尤其是对于后来的 JVM。 JNI 调用是一个安全点,它可以防止优化器内联它们或展开包含它们的循环(或多或少)。有了内在函数,优化器就可以像对待任何其他 Java 方法一样对它们进行推理。我已经看到 optmiser 通过内联删除了几层方法调用并展开一个外部循环,以便 sun.misc.unnsafe.setLong() 达到了我们在配置文件引导的优化 C 程序中看到的相同速度。坦率地说,由于在 C 和 C++ 中很少使用 profiled guide 优化,因此 Java 和 sun.misc.unsafe 实际上最终可以比等效的 C 更快。说完之后我总是想吐舌头——不知道为什么。

纯粹主义者有时会讨厌 sun.misc.unsafe,正如这篇现在相当臭名昭著 的帖子所揭示的那样。

“让我直言不讳——sun.misc.Unsafe 必须死于火灾。这是 — 等等 — 不安全。它必须去。忽略任何一种理论绳索, 开始通往正义的道路/now/。距离 JDK 8 的公开更新结束还有很多年 ,所以我们有 /*years */ 来正确解决这个问题 。但是,将我们的头埋在集体沙子中并希望 对 Unsafe 进行微不足道的解决是行不通的。如果您正在使用 Unsafe,今年是解释 API 在哪里损坏并获取它的一年 直接......

请帮助我们杀死 Unsafe,杀死 Unsafe dead,正确杀死 Unsafe,并尽快这样做 以造福于每个人。”

好吧,正如我们在英格兰所说的那样,“这不会发生,伙计。”正如这篇文章所示,它无处不在,而且无处不在。我个人的oss音频合成程序Sonic Field使用sun.misc.unsafe来直接访问内存映射文件 inside mapped direct by buffers。不仅如此,它还会将更大文件中每个内存映射段的地址存储到堆外(malloc'ed)内存中。所有这些代码听起来可能都很慢,但由于允许内联的内部函数,它比直接使用直接映射的字节缓冲区快得多。此外,由于此内存未被垃圾回收,因此它不会在虚拟地址空间中移动,这有助于优化 CPU 数据缓存的使用。

就像我的应用程序一样,有无数程序使用 sun.misc.unsafe 来让 Java 与 C、C++ 等竞争,有时甚至击败 C、C++ 等。至少 JDK/JVM 开发人员现在已经意识到了这一点。请注意,他们的部分解决方案——变量句柄——非常笨拙(正如我在文章开头所建议的——Java 似乎正朝着这个方向发展)。但是,如果它在管理内存栅栏和原子方面真的(或变得)和 sun.misc.unsafe 一样快,那么笨拙可以隐藏在库中。好消息是开发人员已经意识到真正的社区需求并且不再喝抽象/功能酷的援助(一点点)。有些人希望更好、更快的 Java 仍然存在。虽然我很失望地看到在 varhandles 中没有适当的堆外支持的证据。希望这会到来,或者存在但不知何故隐藏(请随意评论您的想法)。

通用程序员的泛型

我有点理解现在擦除同质结构参数类型是什么类型——这花了很多年。

Java 在 Java 5 中大张旗鼓地添加了泛型;毫无疑问,这是对 Java 的一大改进,尤其是与自动装箱结合使用时。突然之间,从程序员身上卸下了类型套管和装箱值类型到引用类型的巨大负担。通过这样做,Java 的类型系统变得几乎完好。换句话说,如果编译器能够“看到”通过泛型使用的所有类型,那么程序将(几乎)保证只要编译就永远不会抛出类转换异常。

如果您从未对 Java 前泛型进行过编程,那么可能很难想象旧类型系统的后部有多痛苦。例如,像 Vector 这样的容器是无类型的;它包含索引对象。 Java 中的所有引用类型都是 Object 的子类型,因此 Vector 可以包含任何引用类型;确实是任何东西的混合物。可怜的笨蛋程序员必须在使用 Vector 之前将从 Vector 检索到的内容转换为适当的类型。更糟糕的是,程序员必须确保只有合适的类型才能进入 Vector;在具有异构编程团队的复杂系统中,后一步是一个挑战。

不用说,ClassCastException 一直是 Java 程序的祸害。如今,IDE 在警告甚至防止容易出现意外 NullPointerExceptions(主要)的用法方面做得很好,而泛型则摆脱了 ClassCastExceptions(主要)。早在 2000 年代初期和编程之前,Java 有四个阶段:

  1. 编写代码。
  2. 编译代码
  3. 花很多很多小时/周/天来修复 ClassCastExceptions 和 NullPointerExceptions。
  4. 让它通过单元测试——多次返回 4。

所有这些通用的东西(除了 - 天哪我是外卡是什么?当我们在做的时候,什么是类型擦除?

我觉得我必须知道并且自然而然地我必须使用这两个概念来证明我作为 Java 程序员的能力。除了,它们有点棘手。现在我有 2 个 JVM 编译器,并且还从事商业 C++ 编程工作,我想我对什么是类型擦除有一个很好的了解。此外,Java 并没有真正使用类型擦除(别喊)。实际发生的是类型在执行的字节代码中被擦除;带注释的字节码仍然有类型。换句话说,我们依靠编译器来获得正确的类型,而不是运行时,并且编译器不会在 AST/类型系统级别擦除类型。例如,当 C++ 内联方法时也是如此。内联方法的类型在编译期间被完全擦除,但将保留在调试信息中(至少对于现代版本的 C++)。但是,我们不称这种类型为擦除。有趣的是,现实和象牙塔式的讨论如此遥远(我猜是通过象牙塔的高度)。

外卡是另一个问题。我发现它们与 monad 一样无法发挥作用。我可以理解通配符或简单的单子,但在现实世界中,我需要完成工作,因此不值得付出努力的认知负担。

例如,让我们看一下有关该主题的一些 Oracle 文档

List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35)); // compile-time error

但是,以下要简单得多:

List<NaturalNumber> ln = new List<>();
ln.add(new NaturalNumber(35)); // This is fine.

我什么时候可能真正需要在真实程序中使用通配符行为?即使我确实需要它,以下内容也有效:

class ConcreateNaturalNumber() extends NaturalNumber{}
class EvenNumber extends NaturalNumber{
  // Stuff
}
List<ConcreateNaturalNumber> ln = new List<>();
ln.add(new NaturalNumber(42)); // Compile time error.

一种看待这个问题的方法是 List 隐式定义了一个新类型;该类型是“NaturalNumber 的任何孩子”。虽然这似乎是使类型系统完整的好方法,并且可能对库开发人员有用,但对于像我这样的普通人来说,如果我想要一个新类型,为什么不显式地创建它呢?

因此,由于类型擦除和通配符的嵌入式概念,泛型看起来极其复杂。然而,随着时间的推移,Java 社区已经学会主要关注泛型的一个子集,它使用显式类型并且在很大程度上忽略了擦除(让编译器和运行时在幕后进行)。因此,现在像我这样的泛型程序员可以使用泛型,而不必担心极端情况和复杂的类型规则。

这是我非常喜欢 Java 社区的一点;它喜欢追求有用的东西。这与我在 C++ 世界中看到的形成鲜明对比,在 C++ 世界中,人们寻找每一个可以被利用的奇怪边缘案例,然后这样做只是为了证明他们足够聪明。

当我输入类型时,Java 类型在输入时必须理解哪些其他类型的类型?

我们很容易陷入这样一种错觉,即对象层次和主格参数类型就是 Java 所做的一切;但事实并非如此。

随着反射 API 的引入,Java 在 1997 年(是的,真的)摆脱了面向对象。为了更好地感受当时的感觉,这篇文章 与该版本同时发布(它讨论了 Java bean——您还记得那些吗?)。突然之间,Java 有了完整的鸭子类型。换句话说,我们可以去查找一个类的方法并调用它,而不需要知道类的类型,除了它的名称。说有一个方法:

void wagTail(){
   // some stuff.
}

在两个不相关的类中,“CustomerService”和“Dog”。通过反射,CustomerService 和 Dog 的对象都可以在不需要公共基类的情况下摇尾巴(这可能意味着什么——甚至没有暗示契约的概念)。

这对 Java 中的一些基本概念产生了巨大的影响,至今仍具有巨大的影响。有些人(包括我自己)宁愿使用编译时类型检查动态分派的静态类型。其他人(似乎大多数 Java 程序员)希望拥有完整的运行时动态分派并绕过静态类型检查。

当然,带有运行时类型检查的完整运行时动态调度可以工作。例如,Python 在这方面做得很好,因为 Python 程序员习惯于添加额外的鸭子类型管理代码来保持稳定。对于 Java,其影响可能是灾难性的,但实际上(100% 个人观点警告)我怀疑它真正做的是迫使 Junit 和其他 Java 单元测试方法的开发达到他们现在已经达到的非常复杂的水平。如果你查出编译时类型检查窗口,你绝对必须测试代码中的排泄物,而 Java 在这个领域一直处于世界领先地位。

我确实发现了 Maven 和依赖注入一起工作以绝对确定人们完全不知道在任何时候实际执行什么代码的现状,这令人沮丧。话虽如此,它似乎适用于 Java 社区,并且不必以这种方式编写代码(至少我不会使用 Java)。看到 Python 中的数百万行代码库运行良好后,我对运行时动态调度的疑虑有所消散。在这里生活和让生活可能是一个很好的方法。

然而,运行时鸭子类型对于 Java 世界来说是不够的。必须找到更多的类型和调度系统来使 Java 更强大、更笨重、更难理解并且对程序员来说更有利可图!

首先,到目前为止,其中最邪恶的是代码编织。上一堂看似无辜的课,并贴上注释。然后,在运行时,这个类对其代码进行了重新编写,以使其分派给其他代码并完全改变其行为(想想Universal Soldier)。随之而来的是面向方面的编程,它既是横切面又是一个主要问题。我想我不应该太刻薄,毕竟代码编织确实对整个 POJO 和 Spring 运动有所帮助。

我的理解是 Spring 不再需要代码编织。它动态编译代理类而不是向类行为添加方面。从程序员的角度来看,结果大同小异。现在需要非常努力地打破僵局,因为……Spring 和 POJO 通常充当 J2EE/JEE 的平衡物,甚至在 hadoop 成为一件大事之前,帮助 Java 免于缓慢的灰色死亡。事实上,JEE 从 Spring 和方面社区那里学到了一个 bucket load,所以从各方面来看,结果是好的。

JDK 开发人员不满足于所有这些,希望有一些新的类型概念。 首先是类型推断。现在 C# 通过引入 var 关键字开始了这一点。在“非这里发明综合症”的疯狂配合下,Java 与钻石运算符一起出现。这些总比没有好,就像不新鲜的面包总比挨饿好。

让荷马·辛普森 (Homer Simpson) 将其与 <> 水平“半评估”,他们完全厌倦了 Lambdas。从这篇文章我们得到以下例子:

n -> n % 2 != 0;
 (char c) -> c == 'y';
 (x, y) -> x + y;
 (int a, int b) -> a * a + b * b;
 () -> 42
 () -> { return 3.14 };
 (String s) -> { System.out.println(s); };
 () -> { System.out.println("Hello World!"); };

所以“(x,y) -> x + y;”是一个东西但是“var x = 1;”不是。是的,这很有道理。事实上,在 lambda 中进行类型推断真的很棒。如果它们是一阶引用闭包而不是仅支持二阶引用语义(它们有效地关闭最终状态但可以在该状态内改变引用)它们将真正有用。实际上,它们不能保证没有副作用,但它们不是完整的闭包实现。

还不相信二阶引用,试试这个:

LongFunction<Long> broken = chunks -> {reportTicker.set(chunks); return chunks % 10;};

我刚刚检查了这个编译——确实如此。最终(或实际上是最终)reportTicker 对象被 lambda broken 突变。因此,从状态的角度来看,有效的终结性并没有为 lambda 增加任何保证。 Lambda 是多线程上下文中的普通对象,并不比匿名类更容易推理。创建 lambda 的所有努力最终成为围绕匿名类的语法糖(使用 invokedynamic 实现更复杂)。还是不相信?这是使用匿名类编写的上述 lambda。

LongFunction<Long> broken = chunks -> new LongFunction<Long>()
{
    @Override
    public Long apply(long value)
    {
        reportTicker.set(chunks);
        return chunks % 10;
    }
}.apply(chunks);

至少流式接口设计是如此糟糕,fork/join 线程在应用程序中如此狭窄,以至于相比之下,Java lambda 看起来确实非常出色。

如果您不喜欢我在这里所说的,只需使用 C++11 lambda 作为一流的引用闭包,看看这种编程方式是多么非常强大。

那么,这真的必须结束了吗?那些 Java/JDK 开发人员不会去介绍另一个类型系统,对吗?那太疯狂了……

他们做到了——运行时参数化多态性;像一盒青蛙一样疯狂,但最终非常有用。如果 Java 的类型系统还没有成为热力学第二定律 的典型例子——添加一个新的类型/调度系统将是一个非常糟糕的举动,但是这匹马确实已经出局了并在远处的山上建立了一小群野马,所以“为什么不呢?”

VarHandles – 多么有趣:

“调用访问模式方法的参数的数量和类型不会被静态检查。相反,每个访问模式方法都指定一个访问模式类型,表示为 MethodType 的一个实例,用作一种方法签名,根据它动态检查参数。访问模式类型根据 VarHandle 实例的坐标类型和对访问模式重要的值的类型给出形式参数类型。访问模式类型还提供返回类型,通常根据 VarHandle 实例的变量类型。当在 VarHandle 实例上调用访问模式方法时,调用站点的符号类型描述符、调用参数的运行时类型以及返回值的运行时类型必须与访问模式中给定的类型匹配类型。如果匹配失败,将抛出运行时异常。”

除了每次阅读它都变得更有趣之外,我不可能对此添加任何内容。我想我必须去某个地方踢球。

卡夫卡、斯帕克和不可思议的卡桑德拉

第二代云系统现在比比皆是,Java 再次领先。虽然一些云开发正在转向 C++,像 Impala 这样的著名玩家使用一些和 Scylla 只使用这种语言,但仍然可以公平地说大多数 OSS 云基础设施工作是在 Java 中或者在 JVM 上运行。例如,最近几个月似乎从星火变成森林大火的 SPARK 就是用 Scala 编写的。我不确定为什么有人会想要做这样的事情,但它确实有效并且一直在获得牵引力。

随着这些玩家的到来,Java 迎来了光明的未来。过时的深色斗篷无处可寻。尽管我不认为下一个十年是没有挑战的,但我将在下一节中讨论。

整块地面到沙子

Java 和 JVM 从第一天起就融入了一些基本概念。正如我之前所讨论的,其中之一就是资源不对称。另一个是封闭的沙箱。当 Java 最初设计为在小程序中作为受保护的进程运行并且无法从用户源代码访问操作系统时,这确实很有意义。在这个模型中,Java 语言与其开发工具包紧密耦合,必须提供执行所需任务所需的一切。微软将 Azure 设计成没有机器概念和没有 Linux 的纯 .Net 的概念绝对失败说明了这种方法是如何完全不适合云计算的。

计算硬件的变化对 Java 没有帮助。正如我之前提到的,numa 不适合 Java。即使使用支持 numa 的垃圾收集,服务器上一个巨大的 JVM 的性能也会被该服务器的分区特性所扼杀。

具有挑战性:“当所有严肃的计算都需要多台计算机的协作时,大型、多线程、单例 VM 是否有意义。”

考虑一下,要与我现在的雇主一起计算一些严肃的事情需要数万个计算核心。换句话说,计算不是在服务器级别完成的,而是在分布在许多服务器上的核心和程序级别完成的。最终程序员甚至看不到服务器的存在。因此,JVM 成为障碍而不是好处。在许多服务器中的每一台上都有一个巨大的 JVM 合乎逻辑吗?可能不会。但是在一台服务器上运行 32 个小型 JVM 合乎逻辑吗?鉴于 JVM 不是为此而设计的,也不是为在短周期内启动和关闭而设计的,因此在这个领域存在巨大的挑战。

话虽如此——Java 一如既往地在再生。 split varifier 减少了启动时间(好吧——有人告诉我,我在现实中不太确定),现在使用模块可以更好地控制 JDK 大小。因此现在启动/关闭应该更好。但是,由于无法分叉 JVM,因此它永远无法与可以在云中使用分叉和运行模型的其他系统(C++、C、Rust、Python 等)竞争。

我不确定这方面的未来在哪里。可能是在云中运行大型单例 JVM 的挑战不足以阻止人们。如果是这样,Monolith 将继续存在。否则,Java 和 JVM 可能必须再次完全再生才能变得轻量级。那将是一个令人印象深刻的技巧,我个人从未成功过。

聚苯乙烯

以防万一我没有在某个地方冒犯某人,这里有一些我应该详细讨论但觉得咆哮已经足够长的事情:

  • 尝试使用资源:非常好。
  • Maven:可憎。
  • Gradle:我认为没有什么比 make 更糟糕的了,但它已经实现了。
  • Swing:很酷,但网络吃了午餐。
  • nio:刚出来的时候真的很好,但需要尽快完善。
  • Valhalla: 本来可以很棒,但是让值类型不可变会削弱这个概念。具体化的内在通用容器会很好。
  • 调用动态:太静态但有希望。
  • Jmh:太棒了,时间到了。
  • Ant:如果它不是 XML,它将是 4 星(满分 5 星)。
  • 模拟框架:是的——我想是的,但大多数时候它们似乎被过度使用了。
  • G1 垃圾收集器:因为我不相信大型 JVM 有意义,因此不清楚 G1 是否必要,但这绝对不是坏事。
  • JVMTI:太棒了。
  • 内部类:是的,它们是被发明出来的,而不是原始 Java 的一部分,它们很可爱。
  • OSGI:生命太短暂。
  • 拼图:更像它。
  • Scala:很像 Delorean,看起来很酷,但速度慢得离谱,很难上手,而且总是出问题。
  • 其余的对不起,我忘记了你,Java 是如此之大,有太多东西需要忘记
标签2: Java教程
地址:https://www.cundage.com/article/jcg-20-years-java.html

相关阅读

Java HashSet 教程展示了如何使用 Java HashSet 集合。 Java哈希集 HashSet 是一个不包含重复元素的集合。此类为基本操作(添加、删除、包含和大小)提供恒定时间性...
SpringApplicationBuilder 教程展示了如何使用 SpringApplicationBuilder 创建一个简单的 Spring Boot 应用程序。 春天 是用于创建企业应...
通道是继 buffers 之后 java.nio 的第二个主要新增内容,我们在之前的教程中已经详细了解了这一点。通道提供与 I/O 服务的直接连接。 通道是一种在字节缓冲区和通道另一端的实体(通...
课程大纲 Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式的、支持多租户的全文搜索引擎,带有 HTTP Web 界面和无模式的 JSON 文档。 Elasti...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Java 是用于开发各种桌面应用程序、Web 应用程序和移动应用程序的最流行的编程语言之一。以下文章将帮助您快速熟悉 Java 语言,并迈向 API 和云开发等更复杂的概念。 1. Java语言...
Java中的继承是指子类继承或获取父类的所有非私有属性和行为的能力。继承是面向对象编程的四大支柱之一,用于提高层次结构中类之间的代码可重用性。 在本教程中,我们将了解 Java 支持的继承类型,...
Java Message Service 是一种支持正式通信的 API,称为 网络上计算机之间的消息传递。 JMS 为支持 Java 程序的标准消息协议和消息服务提供了一个通用接口。 JMS 提...
之前,我介绍了spring 3 + hibernate 集成 示例和struts 2 hello world 示例。在本教程中,我将讨论在将 spring 框架与 struts 与 hibern...
Java 项目中的一项常见任务是将日期格式化或解析为字符串,反之亦然。解析日期意味着你有一个代表日期的字符串,例如“2017-08-3”,你想把它转换成一个代表 Java 中日期的对象,例如Ja...