二十年前,在苏黎世的一间公寓里发生了两件事。
我的女儿迈出了第一步,一位年轻的博士后研究员(她的父亲)迈出了使用Java的第一步。 很难完全了解当时的Java。 在这些时代,TCL盛行,Java时代与冰箱和烤面包机之间有些奇怪的关系。 Java没有明显的用途,但后来以某种方式获得了发展,就像陡峭的下降梯度上的蒸汽火车一样。
首先吸引我使用该语言的实际上是小应用程序。 在这些“新的和所有流行的”网页之一中嵌入分子结构的实时3D唾液化的想法似乎很令人陶醉。 同时,对于Fortran和C程序员而言,Java似乎是一种难以想象的笨拙和低俗的语言。
在接下来的20年中,我从未离开Java超过几个月。 它改变了计算机世界,部分原因是它打破了IT在Microsoft鼎盛时期非常喜欢的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与Who的相似之处
Java并不是持续成功的动力源,但它肯定是成功的动力。 我们可能希望相信它的进展是集中的和计划的,而对某些主流Java开发的完全失败以及“声音消失”所取得的惊人成功却视而不见。
每当Java和JVM由于某种克星(C#,Go,Ruby等)而濒于灭绝的边缘时,都会发生再生,从而导致一系列令人兴奋的事件。 甚至JNI接口之类的可怕伤口,或令人痛苦的并行执行器流媒体混乱,都不足以杀死我们的英雄。 同样,在Java 7、8和9中引入了显着的性能增强(例如,热点VM)和广泛的编译器优化技巧,这些都使Java在一个CPU时钟速度停滞且崩溃后的IT预算渴望节省成本的世界中一直保持着相关性。 。
转义分析是否有heledp Java转义成本分析? (好的,那是一个太多的亚历克斯,用白衣退缩。)
尽管回顾的自然趋势是顺应时代潮流,但我发现Java面临着巨大挑战。 除了其他在商业上最重要的语言C,Fortran和COBOL之外,Java的历史也像其运行时和递归一样具有多线程性,因为外部力量使Java弯曲,而Java同样重塑了IT领域。
为了说明这一点,我们可以看一下JEE和Hadoop。
大象和鱼
在世纪之交,编程变得有些疯狂。 某些事情本来应该非常简单,例如服务于网页,突然需要(感觉)XML页面和Java代码段(仅仅是为了定义“ Servlet”)。 该servlet将在“应用程序服务器”中进一步得到支持,该服务器具有更多的XML定义Java Bean,这些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,然后通过序列化来在它们之间进行通信。
该系统进一步依赖于当时在Weblogic中具有灾难性的JMS(如果我没有记错的话,它是第5版)。 实际上,我们从Weblogic JMS实施开始就是使用blob类型将消息序列化到Oracle,而Oracle 8i无法管理内部事务。 是的,确实如此,JMS消息持久性不是事务性的,但他们仍然为此垃圾索要钱。
因此,我花了6个月的时间从J2EE中提取业务逻辑代码,并以现在称为POJOS(Java对象的纯文本)的方式实现它们。 我走得更远,并用基于PL / SQL的消息传递系统替换了JMS,该消息传递系统是使用PL / SQL到Java的绑定从Java访问的。 所有这些都运行良好,并且比J2EE系统快许多倍。
然后,我的一个朋友和同事用PL / SQL重写了整个过程,而且速度甚至更快。
从那时起,这可能会破坏我对J2EE的看法,您可能不会感到惊讶。 它的基本故障是痴迷于复杂的,缓慢的抽象以及应用服务器的概念。 这些都不是必需的。
就在JEE的重量级似乎使大型商务Java陷入漫长的缓慢死亡之际,Google用其关于GFS,Map-Reduce和BigTable的著名论文震惊了世界。 Google文件系统及其上运行的系统为处理提出了新的思路。 运行服务器然后运行进程的计算机的“体现”编程模型消失了。 此外,整个方法的概念还很低。 在庞大的计算资源冗余“云”中运行简单的事情。 但是,这些“事物”所具有的规定性远低于紧密连接和抽象的JEE世界。
我们的“话语权”不是屈服于这种新的敌人,而是使Java再生为全新的野兽。 Hadoop诞生了,而不是云成为企业中Java的灭亡,它在可预见的将来已将Java嵌入该企业中。
手机是新冰箱
我相信,所有人都应该对Java表示感谢,这是使平台独立性成为开发人员意识的一件事。 将软件开发基本独立于操作系统供应商的炒作,彻底改变了高层系统架构的思想。 可以在Windows上写一些东西并在Linux(或Solaris或Irix或任何其他版本)上运行它的想法只是在90年代后期就融化了。
我个人认为,Java平台独立性与Hadoop坚固耐用性的结合是防止Microsoft使用.Net占领世界的两大力量。
这种平台独立性从何而来? 过去的基本目的是什么? 好吧,我们可以重写历史记录,并在事后说些不同的话。 但是,我清楚地记得Sun所说的一切与冰箱和烤面包机有关。 他们完全以某种方式确信自动化设备是未来(正确),而Java将是编写一个设备管理程序并在任何地方运行它的方式(错误)。
把第二部分弄错几乎不是一个重大的失败。 Sun不可能预测运行稳定的开源操作系统的超低成本CPU将证明是对虚拟机的抽象选择。 Linux通过在OS级别上提供平台独立性和免费提供,从而彻底颠覆了整个世界。 但是,这是另一个故事,而不是Java的故事。 相反,出现了Android。
许多业务Java开发人员并未真正考虑Android的影响,因为它不运行JVM。 但是,它确实运行Java。 现在(据我所知)情况发生了一些变化,但是甚至在5或6年前,开发Android应用程序的标准方法就是使用Android模拟器在PC上用Java编写该应用程序,并将其编译为字节代码,然后将JVM叮咬代码交叉转换为Dalvik字节代码。
确实,此过程非常出色,以至于当我使用Microfocus时,我们将COBOL编译为JVM字节码,然后将其翻译为Dalvik,然后在Android手机上运行了COBOL应用程序。 我并不是说这是一件好事,但确实很有趣。
我的观点是,Android(以及在此之前程度较小的Java功能手机)使Java与庞大的新兴开发者社区相关。 我怀疑由于Android,大学现在教Java而不是C#。 再说一次,“声音关闭”保存了Java,并使其重新生成了一个新的Doctor,从而在一个激动人心的新系列中接受了新的挑战(实际上-我不看Who博士-我在70年代和80年代做过;当Lalla Ward和Tom Baker离开系列赛时,我有点失去兴趣了 。
我回想起有关“ Android是否是正确的Java”的讨论,以及Google和Oracle之间的敌对情绪,这使我有些不安。 毫无疑问,谷歌采用Dalvik和Java作为Android平台的事实极大地提高了Oracle拥有的Java资产的价值。
简洁优雅– JMM
Java很少被视为开拓性的简洁性和优雅性,但从某种意义上说,它确实向其他主流语言指明了前进的方向。 作为Java 5标准的一部分,引入新的Java内存模型是对简单性和有效性的一次胜利。
让我们认真考虑一下它的大小。 大型商业编程语言中的一种首次以清晰的术语列出了多线程环境中该语言的所有“先发生”关系。 对边缘案例的所有担忧都消失了; 由于试图保持行为之间的相似性而遗漏了所有优化,而这些行为从未被最初指定。 突然,Java成为开发无锁和无锁算法的“通行语言”。 有关跳过列表实现之类的术语的学术论文可能基于Java。 此外,该模型随后渗透到基于JVM的任何其他语言。
其他JVM语言不受其影响的限制; 引用维基百科:
“ Java内存模型是为流行的编程语言提供全面的内存模型的首次尝试。 [5]并发和并行系统的日益普及以及对此类系统提供具有清晰语义的工具和技术的需求是有道理的。 从那时起,内存模型的需求已被更广泛地接受,并且为诸如C ++之类的语言提供了类似的语义。 [6] “
因此,是的,Java教会了C ++如何进行内存建模,我感受到了Java 5和C ++ 11的影响。
不安全,但需要任何速度
自从热点最终使编译/解释落空以来,Java的致命缺陷一直是而且很可能永远是其资源分配模型。 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开发人员需要对计算机硬件的更多低级访问,因此他们不断向这个秘密秘密添加内容。
回到我为Morgan Stanley工作时,我参与了一个项目,该项目使C ++低延迟系统通过共享内存与Java“对话”。 为了确保在Intel x86上使用C ++ 11标准和sun.misc.unsafe进行原子操作的方法相同,我使用了开放的JDK本机代码。 确实,尽管某些sun.misc.unsafe操作不太理想(例如,在CAS上循环进行原子写入,而不是使用aa锁前缀移动),但是在写入时使用篱笆的方法和依赖于与命令1匹配的有序读取: 1与C ++ 11。
因为sun.misc.unsafe方法是固有的,所以它们的性能非常好,尤其是对于更高版本的JVM。 JNI调用是一个安全点 ,可以防止优化程序内联它们或展开包含它们的循环(或多或少)。 使用内在函数,优化器可以像对待其他Java方法一样对它们进行推理。 我已经看到optmiser通过内联除去了几层方法调用,并展开了一个外部循环,使sun.misc.unnsafe.setLong()达到了与配置文件引导的优化C程序相同的速度。 坦率地说,由于在C和C ++中很少使用剖析式指南优化,因此Java和sun.misc.unsafe实际上可以比同等的C更快地结束。我一直想说完之后就伸出舌头-不知道为什么。
纯粹主义者有时会讨厌sun.misc.unsafe,因为这已经臭名昭著了。
“让我直言不讳-sun.misc.Unsafe必须在大火中死亡。 是-等待
为此-不安全。 它必须走。 忽略任何一种理论上的绳索和 开始通向公义/ now /的道路。 距离 JDK 8的公共更新结束,所以我们有/ * years * /来解决这个问题 正确地。 但是,将我们的头放在集体的沙滩上,并希望 不安全的琐碎工作将无法进行。 如果您正在使用 不安全,这是一年解释API损坏的地方并获取它 直行…。
请帮助我们杀死不安全的人,杀死不安全的人,杀死不安全的权利并采取行动
以便尽快使所有人都受益。”
好吧,正如我们在英格兰所说的那样,“这不是事实。” 如该帖子所示 ,它无处不在,无处不在。 我的个人oss音频合成程序Sonic Field使用sun.misc.unsafe 直接访问由缓冲区直接映射的内存映射文件 。 不仅如此,它还将每个内存映射段的地址存储在一个较大的文件中,放入堆外(已分配内存)。 所有这些代码听起来似乎都很慢,但是由于内部函数允许内联它的结束比直接使用直接映射的字节缓冲区快得多。 此外,由于该内存不是垃圾收集的,因此不会在虚拟地址空间中移动,这有助于优化CPU数据缓存的使用。
就像我的应用程序一样,有无数的程序使用sun.misc.unsafe来允许Java竞争甚至有时击败C,C ++等。至少JDK / JVM开发人员现在已经意识到了这一点。 请注意,它们的部分修正( 可变句柄 )令人头脑笨拙(正如我在文章开头所建议的那样,Java似乎正朝着这种方向发展)。 但是,如果它真的可以(或变得)与sun.misc.unsafe一样快的速度来管理内存围栏和原子,那么笨拙就可以隐藏在库中。 好消息是,开发人员已经意识到社区的实际需求,并停止饮用抽象/功能酷炫的辅助设备(有点)。 有些人希望有更好,更快的Java。 尽管令我失望的是,目前还没有证据表明在varhandles中有适当的堆外支持。 希望它会来临,或者在那里,但是会以某种方式隐藏起来(请随意评论您的想法)。
泛型程序员的泛型
我有点理解现在是什么类型的擦除均质结构参数化类型-已经花费了很多年。
Java在Java 5中增加了泛型,引起了广泛的关注。 无疑,这是对Java的重大改进,尤其是与自动装箱结合使用时。 突然,程序员从类型中删除了类型框和装箱值类型对引用类型的巨大负担。 这样一来,Java的类型系统几乎变得很健全 。 换句话说,如果编译器能够“查看”通过泛型使用的所有类型,那么只要保证程序被编译,它(几乎)将永远不会抛出类强制转换异常。
如果您从未编程过Java泛型,那么可能很难想象旧类型系统在后部带来的痛苦。 例如,未键入Vector之类的容器; 它包含索引的对象。 Java中的所有引用类型都是Object的子类型,因此Vector可以包含任何作为引用类型的内容。 确实是任何东西的混合物。 可怜的schmuck程序员必须在使用Vector之前将从Vector检索到的所有内容转换为适当的类型。 更糟糕的是,程序员必须确保只有适当的类型才能将其放入Vector。 在具有异构编程团队的复杂系统中,后一步是一个挑战。
不用说,ClassCastException一直是Java程序的烂摊子。 如今,IDE可以很好地警告甚至防止易于发生意外NullPointerExceptions的用法,而泛型则摆脱了ClassCastExceptions的困扰。 早在2000年代初期,在编程之前,Java就经历了四个阶段:
- 编写代码。
- 编译代码
- 花很多很多小时/几周/几天来修复ClassCastExceptions和NullPointerExceptions。
- 使其通过单元测试-多次返回4。
所有这些通用的东西(除了- 我的优点是通配符外,其他什么都很棒?虽然我们在使用它,什么是类型擦除?
我觉得我必须知道,自然地我必须使用这两个概念来证明自己是一名Java程序员。 除了,他们有点棘手。 现在我拥有2个JVM编译器,并且还从事商业C ++编程工作,我想我对什么类型的擦除有一个很好的了解。 此外,Java并没有真正使用类型擦除(请不要大喊大叫)。 实际发生的是在执行的字节码中删除了类型; 带注释的字节码仍然具有类型。 换句话说,我们依靠编译器来获取正确的类型而不是运行时,并且编译器不会在AST / Type-System级别上擦除类型。 例如,C ++在内联方法时也是如此。 内联方法的类型在编译过程中会完全删除,但会保留在调试信息中(至少对于现代C ++版本而言)。 但是,我们不称这种类型的擦除。 有趣的是,关于现实和象牙塔类型的讨论如此遥远如此频繁(我猜是名义塔的高度)。
通配符是另一个问题。 我发现它们像单子一样抵制有用性。 我可以理解通配符,也可以简短地理解monads,但是在现实世界中,我需要完成工作,因此独占的认知负担不值得付出。
通过示例,让我们看一下有关该主题的一些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>隐式定义一个新类型; 该类型为“ NaturalNumber的任何子代”。 虽然这似乎是使类型系统完整的一种好方法,并且可能对库开发人员有用,但对于像我这样的简单凡人,如果我想要一个新类型,为什么不显式创建它呢?
因此,由于类型擦除和通配符的嵌入式概念,泛型似乎极其复杂。 但是,随着时间的流逝,Java社区已经学会了将注意力集中在泛型的子集上,该子集使用显式类型并且在很大程度上忽略了擦除(只要让编译器和运行时在后台进行操作即可)。 因此,如今像我这样的泛型程序员可以使用泛型,而不必完全担心极端情况和复杂的类型规则。
我真的很喜欢Java社区。 它喜欢去工作。 这与我在C ++世界中看到的相反,在C ++世界中,人们寻找可以被利用的每个奇怪的边缘情况,然后这样做只是为了证明他们足够聪明。
虽然我在键入Type,但是键入时Java类型还必须理解其他什么类型的Type?
我们很容易陷入一种幻想,即Java所做的就是对象分层和名义参数类型化。 但事实并非如此。
Java在1997年通过引入反射API脱离了面向对象(的确是)。 为了对当时的感觉有一个很好的感觉, 本文与发行版是同时代的(谈论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更加强大,笨拙,难以理解并且对程序员有利可图!
首先,到目前为止,最有害的是代码编织。 参加一个天真无邪的课堂,并坚持注解。 然后,在运行时,此类具有非常出色的代码重用性,可使其分派给其他代码并完全改变其行为(认为通用士兵 )。 随之而来的是面向方面的编程,这既是跨领域的又是一个主要问题。 我想我应该不太烦人,毕竟所有代码编织都为整个POJO和Spring运动提供了一定的帮助。
我的理解是Spring不再需要代码编织。 它动态地编译代理类,而不是向类行为添加方面。 从程序员的角度来看,结果大致相同。 现在需要非常努力地打破休息,因为……Spring和POJO通常起到抵消J2EE / JEE的作用,甚至在Hadoop没什么大不了之前,就帮助Java摆脱了缓慢的灰亡。 实际上,JEE从Spring和Aspect社区那里学到了很多东西,因此,结果是不错的。
对所有这些都不满意,JDK开发人员希望拥有一些新的类型概念。 首先是类型推断 。 现在,C#通过引入var关键字从此开始。 在“这里没有发明综合症”的疯狂拟合中,Java与钻石运营商一起使用。 从说面包比挨饿更好的角度说,这些总比没有好。
让荷马·辛普森用<>将它“ 弄平 ”了一半 ,他们对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。 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实例的坐标类型和对访问模式重要的值的类型给出形式参数类型。 An access mode type also gives a return type, often in terms of the variable type of a VarHandle instance. When an access mode method is invoked on a VarHandle instance, the symbolic type descriptor at the call site, the run time types of arguments to the invocation, and the run time type of the return value, must match the types given in the access mode type. A runtime exception will be thrown if the match fails.”
I could not possibly add anything to this other than it gets more amusing each time I read it. I guess I have to get my kicks someplace.
Kafka, Spark And The Unbelievable Cassandra
Second generation cloud systems are now abounding and Java is once again leading the pack. Whilst some cloud development is moving to C++ with notable players like Impala using some and Scylla using only this language it is still fair to say most OSS cloud infrastructure work is either in Java or runs on the JVM. For example, SPARK which seems to have grown from a spark to a forest fire over recent months is written in Scala. I am not sure why anyone would want to do such a thing, but there it is and it works and is gaining traction all the time.
With these players comes a bright future for Java. Obsolescence's dark cloak is no where to be seen. Though I do not view the next decade as challenge free as I will discuss in the next section.
Monolith Ground To Sand
Java and the JVM have some basic concepts baked into them from day one. As I discussed earlier, one of these is resource asymmetry. Another is a closed sandbox. This really made sense when Java was originally designed to run as a protected process in an applet and had no access to the OS from user source code. In this model the Java language coupled tightly to its development kit had to provide everything required to perform desired tasks. Microsoft's absolute failure of concept in designing Azure to be pure .Net with no concept of machines and no Linux illustrates how this approach is utterly inappropriate for cloud computing.
Changes in computational hardware are not helping Java. As I mentioned previously, numa is a poor fit for Java. Even with numa aware garbage collection, the performance of one huge JVM on a server is strangled by the partitioned nature of that server.
To be challenging: “Does a large, multi-threaded, singleton VM make any sense when all serious computing requires the collaboration of many computers.”
Consider this, to compute something serious with my current employer requires tens of thousands of compute cores. In other words, computations are not done at the server level but at the core and program level distributed across many servers. That there are even servers present is not seen by the end programmer. As such, the JVM becomes a barrier not a benefit. Is it logical to have one huge JVM on each of many servers? 可能不是。 But then is it logical to have 32 small JVMs running on a server? Given that the JVM is not designed to do this and is not designed to be started up and brought down in short cycles, there are huge challenges in this area.
Having said that – as always Java is regenerating. Start up times were reduced by the split varifier (well – I have been told that, I am not so sure in reality) and JDK sizes are now being controlled better using modules. As such startup/shutdown should be better now. However, as one cannot fork a JVM, it will never be able to compete with other systems (C++, C, Rust, Python etc) which can use a fork and run model in the cloud.
I am not sure where the future lies in this regard. It could be that the challenges of running large singlton JVMs in the cloud are not enough to deter people. If this is so, the Monolith will continue. If not then Java and the JVM might have to fully regenerate once more to become light weight. That would be an impressive trick which I for one have never yet managed to pull off.
PS
Just in case I have not offended someone someplace, here are a bunch of things I should have discussed at length but felt the rant had gone on long enough:
- Try with resources: Excellent.
- Maven: Abomination.
- Gradle: I did not think something could be worse than make, but it was achieved.
- Swing: Cool but the web ate its lunch.
- nio: Really good when it came out but needs a good polish up soon.
- Valhalla: Could have been great but making value types immutable cripples the concept. Reified intrinsic generic containers will be good.
- Invoke dynamic: Too static but has promise.
- Jmh: Brilliant and about time.
- Ant: If only it was not XML it would be 4 out of 5 stars.
- Mocking frameworks: Yes – I guess so but most of the time they seem over used.
- G1 Garbage collector: As I am not convinced huge JVMs make sense, thus it is not clear G1 was necessary but it is definitely not a bad thing.
- JVMTI: Awesome.
- Inner Classes: Yes they were invented and not part of the original Java and they are lovely.
- OSGI: Life is too short.
- Jigsaw: More like it.
- Scala: Much like a Delorean, looks really cool but is ridiculously slow, hard to get started and breaks all the time.
- The rest: Sorry I forgot about you, Java is so huge there is necessarily so much to forget about
翻译自: https://www.javacodegeeks.com/2018/01/20-years-java.html