为什么Java仍旧生机盎然——对“为什么Java正在消亡”的回应

5,693 阅读23分钟

0. 阅读完本文你将会

  • 了解Java作为热门语言之一所面临的争议
  • 了解Java的生态环境和未来

1. 前言

原文标题:Why Java Is Perfectly Alive——A response to "Why Java Is Dying"

原文地址:betterprogramming.pub/why-java-is…

原文作者:Ivan Khodyrev

注:文中所有图片均来自原文

译者按:编程语言界一直很热闹,各种鄙视链,口水战层出不穷,Java作为长盛不衰的热门语言之一也避免不了诸多争议。今天我们从国外作者的视角来看看这篇1800赞的热门文章,是怎么为Java做"无罪辩护"的。

2. 正文

我写了这份长篇读后感,以此作为对 "为什么Java正在消亡 "这篇文章的回应。我在这篇文章下面的评论已经置顶了,我想我应该写一篇全方位的分析文章。

对于原作者文章最好的简短回答其实是Coder:76的回答,他的回答得到了数百赞。

对于一些想要获得关注的博主来说,Java这15年以来一直处于濒死或者已死的状态。

我对此非常赞同。

2.1 "Java正在消亡"的说法有什么不妥?

那篇"为什么Java正在消亡"的文章收到了70多条评论,其中大部分是批评,每条都有几十、几百个赞。为什么这么多人的评论如此负面?原因很简单。这篇文章写得很有挑衅性,包含了许多有争议的说法,对于使用Java的人来说,这些说法与现实的状况相去甚远。让我们来看看其中的一些内容。

"例如,Spring配置了自动注入(bean injections),这是可以理解的,但是,Lombok在应用环境中处于什么位置,两者之间的消息传递是如何协调的?"

对于使用上述技术的人来说,这种说法看起来是错误的。Lombok是一个编译时库,Spring是一个运行时库。它们在应用生命周期的不同时期工作在不同的层面上,并不直接互动。作者的问题 "Lombok在应用环境中处于什么位置?"的正确答案是"没有位置"。

"Java的重点似乎还是在愚蠢的规则上,这些规则规定了类名应该是什么,它们应该在什么包里,以及变量应该是私有的还是保护的。说真的,谁在乎呢?"

从事大型、长期项目工作的人很在意。这些规则对他们来说并不傻。

"相比之下,'我们都是成年人'简直就是Python对语言中没有访问指定器的官方回应。"

在团队中有人假设别人不是成年人,这没有什么问题——这是一个浅显的想法。问题是,持续时间长、由大团队创建的大型项目需要规则;否则,它们会失败。

一个大型项目就像一个大型城市。它需要建筑基础、规划、关注点的分离、私人和公共区域。如果一个熟练的程序员将语言结构分为公共和私人,他们很可能创造出 "街道",将其他人引向正确的方向,节省他们的时间,并将辅助的基础设施隐藏在"地下",这样就不会有人在那里迷路。

在"为什么Java正在消亡"这篇文章中,还有很多有争议的说法,但我在这里的目标不是详细分析。我想做的是利用这个机会谈一谈Java现在的状况。

多年来,Java是编程语言中的首选之一,同时也是批评家的鞭挞对象。不是因为它不好,而是因为它是个惹人注意的目标,如果你想让自己获得更多的关注,你就必须说反对它的话,并祈祷有人会注意到。从这个角度来说,Java是一个很好的目标。

但是现在呢?Java仍旧是一个香饽饽?还是像有些人说的那样正在"死亡"?让我们来讨论一下最重要和最有争议的话题,以便弄清楚这个问题。

2.2 语法

通常,Java的语法被批评得最多:"不简洁"、"写法过时"、"太多的模板",等等。对于这些"论点",唯一正确的答案是展示代码。我不会在这里讨论特殊的语法特征,有很多详细的指南,涵盖了Java语法的所有细微差别。相反,我选择了五个代码片段,只是为了让你了解现在的Java在不同的实际任务中是如何运作的。

import static spark.Spark.*;
public class HelloWorld {
    public static void main(String[] args) {
        port(80);
        get("/hello", (request, response) -> "Hello World");
    }
}

你可能听说过以前的"好"日子,一个简单的Java网络服务器需要几百行代码和配置才能运行?现在忘掉它们吧。

这段代码使用Spark Java在80端口启动了一个简单的网络服务器,采用HTTP GET方法和/hello上下文路径,在请求时返回一个常量字符串。非常直接和简洁,不是吗?

...
OrderRepository orders;
QOrder qorder = QOrder.order;
DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern("yyyy-MM-dd");

public Iterable<Order> getShopOrdersByDate(ShopId id, ZonedDateTime date){
    return orders.findAll(
      qorder.shopId.eq(id).and(qorder.date.eq(yyyyMMdd.format(date)))
    );
}
...

一些人说,"Java不是为数据库操作而生的"。他们肯定是在开玩笑。

这段代码使用QuerydslSpring Data的组合,从SQL数据库中获取信息。一切看起来都很简单:getShopOrdersByDate方法返回某个特定商店在特定日期的订单,并带有额外的日期格式化。

有趣的是,这里没有SQL,只有Java结构,这些结构稍后会被翻译成库中的安全SQL。这意味着查询本身是类型安全的,并且会可靠地在编译时被检查,而不是在运行时随机检查。另外,IDE会帮助你自动完成,就像其他的Java代码一样,使你的工作更轻松。

大量的数据库开箱即用,如PostgreSQL、MySQL、Oracle,甚至MongoDB。另外,如果你想要都尝试它们看看,你不必改变查询代码。

import java.nio.file.Files;
import java.util.stream.Stream;
import static java.nio.file.Paths.get;
import static java.util.stream.Collectors.*;

public class Example {
     public static void main(String[] args) throws Exception {
        List<String> fileLines = Files.readAllLines(get("huge.txt"));
        String fileStats = fileLines.parallelStream()
                .flatMap(line -> Stream.of(line.split("\\s+")))
                .filter(word -> !"dumb".equalsIgnoreCase(word))
                .collect(groupingBy(word -> word.charAt(0), counting()))
                .entrySet().parallelStream()
                .map(letterStats -> letterStats.getKey() + ":" + letterStats.getValue())
                .collect(joining("\n"));
        System.out.println(fileStats);
    }
}

从来没有想过用Java做一个简单的数据分析?我建议你试试。

这里没有额外的库,只有纯Java。

从大文件中读取所有的行,把它们分成独立的词,过滤掉dumb词以保证结果是你想要的,按第一个字母对词进行分组,计算每组有多少个词,对结果分组和计数创建一个字符串表示,最后打印结果。

这是一个可读性和可维护性相当好的过程。当然,所有的事情都是在并行线程中完成的,以实现适当的加速,这样你的16核机器就能对得起它的成本。

在我装有Java 15的4核笔记本上,对于一个填满随机单词的1.2Gb的文本文件,执行这段代码所花费的时间在平均6秒。这对于这么大的文件和直接的未优化代码来说还不错。

...
public MultiLayerNetwork createModel() {
    return new MultiLayerNetwork(new NeuralNetConfiguration.Builder()
            .regularization(true).l2(0.001)
            .learningRate(0.01)
            .weightInit(WeightInit.XAVIER)
            .activation(Activation.RELU)
            .optimizationAlgo(STOCHASTIC_GRADIENT_DESCENT)
            .updater(new Nesterovs(0.9))
            .list()
            .layer(convolution(5, 25).nIn(3).build())
            .layer(maxPooling(2).build())
            .layer(convolution(3, 50).build())
            .layer(maxPooling(2).build())
            .layer(convolution(3, 100).build())
            .layer(maxPooling(2).build())
            .layer(dense(200).dropOut(0.5).build())
            .layer(dense(200).dropOut(0.5).build())
            .layer(outputClassification(10))
            .setInputType(convolutionalFlat(28, 28, 3))
            .backprop(true).pretrain(false)
            .build());
}

ConvolutionLayer.Builder convolution(int filterSize, int filterCount) {
    return new ConvolutionLayer.Builder(filterSize, filterSize)
            .activation(IDENTITY).nOut(filterCount);
}

SubsamplingLayer.Builder maxPooling(int size) {
    return new SubsamplingLayer.Builder(PoolingType.MAX)
            .kernelSize(size, size).stride(size, size);
}

DenseLayer.Builder dense(int size) {
    return new DenseLayer.Builder().nOut(size);
}

OutputLayer outputClassification(int numOfClasses) {
    return new OutputLayer.Builder(LossFunction.MCXENT)
            .nOut(numOfClasses).activation(SOFTMAX).build();
}
...

他们说:"你不能用Java做深度学习"。不,你可以。

这个简单例子展示了如何在学习阶段之前准备神经网络结构的阶段,它使用了Dl4j。那些在深度学习领域的人将会认出很多熟悉的单词。

没有什么了不起的。是的,你也可以用Java做深度学习和机器学习。

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class User {
    String name;
    String surName;
    List<ContactInfo> contacts;
}

龙目岛(译者注:Lombok和Java的名字都来源于印度尼西亚的岛屿)是毗邻爪哇岛的一个岛屿。它们是如此相近,以至于你甚至可以把它们混为一谈。

你讨厌模板?那就用Lombok吧。这个代码片段展示一个功能齐全的数据类,包括getters、setters、equals、hashcode、toString等所有字段,最后是AllArgsConstructor。当然,如果你愿意,你可以覆盖其中任何一个。

来自Java生态系统以外的人通常认为,"Lombok使得Java不再是Java"。我不同意这个观点。Lombok就是Java。它是现代Java生态系统的一部分,使用合法的Java机制来增加其功能,帮助你使用Java代码,几乎没有缺点,并且被许多Java开发者使用和喜爱。有多少人?最受欢迎的Java IDE之一统计,Lombok插件的下载量为1100万之多。

它是一个让Java变得更好的工具,所以从实际意义上讲,它对数百万使用它的人来说就是Java。尽管如此,Java社区中仍有一部分人反对Lombok,他们完全有权利这样做--如果你不喜欢Lombok,没有人强迫你这样做。

现在想象一下这些例子在其他语言中会是什么样子。你可能会发现用更少的字符来达到同样的目标。但这些代码会像Java语言那样可靠、可读、可维护和快速吗?我不认为如此。

与语法有关的另一件重要事情是IDE的支持。 这不是一个抽象的理论问题,比如语言结构有多强大,或者开发人员要写多少代码才能做什么。集成开发环境增加了一个实用层,将所有抽象的问题转换为这个问题:一个特定的任务会花费多少开发者的时间。

使用精心设计的语言并结合现代IDE,开发人员可以比使用更强大或更简洁但对IDE不友好的语言更快地实现他们的目标。由于Java的语法,它是对IDE支持最好的语言之一。它不是太复杂,同时也不是太自由选择,所以IDE可以理解你当前的工作环境,并非常精确地预测你下一步要做什么。

最后要说的是,Java的语法允许使用不同风格的编程。你的代码可以用面向对象的范式来写,对象之间相互作用;也可以用程序化的范式来写,通过命令式程序调用的序列来改变全局状态;还可以用函数式的范式来写,通过函数的编排和应用来实现你的目标。有时人们会区分更多的范式--例如,Java很适合面向切面或基于Actor的范式。Java在如何思考你的任务方面给了你很大的灵活性,所以它为编程范式的转变做好了充足的准备。

总而言之,Java的语法没有任何问题。它相对简单、灵活、富有表现力。而且,它允许IDE以多种方式有效地帮助开发者,大大提高了他们的工作效率。如果你很了解Java,写出清晰的代码,并为你的任务使用正确的工具集,你的程序将是美丽、可维护和简洁的。不要让人们在这个话题上欺骗你

2.3 可靠性

与其他许多技术不同,Java作为一种语言和一个平台,为您提供了值得信赖的可靠性。

Java语言有《Java语言规范》,它是判断Java代码的结构应该如何工作和应该如何使用的主要依据。

为什么它很重要?因为你可以验证你正在做的事情,并以一种严格的、可预测的方式解决问题或争议。对于没有规范的语言,你不能完全确定发生了什么。你可能在手册、博客或语言创造者的推文中找到一些信息碎片,但在一种语言得到规范之前,所有这些信息碎片都没有一个强有力的基础来保证什么。

当然,规范有不同的质量或在细节上也有水平高低,因此可能会遗漏一些东西。但是,有规范的语言比没有规范的语言给你更大的信心——你的工作是正确的。Java的规范是非常深入和详细的,它几乎没有任何含糊的地方。

Java版本对规范和公共Java API中的东西是强烈向后兼容的。这意味着,如果你把使用20年前写的1.3版本的公共Java API的代码,今天在Java 15上运行,它就可以正常工作。

同时,如果你使用私有的Java API,其中包括不应该被开发者直接使用的类和方法/字段,你可能会有麻烦。我认为这是相当公平的交易:如果你使用的是有向后兼容性保证的东西,你可以依赖它,但如果你使用的是不打算使用的东西,请不要指望它们能永远工作。

Java是安全的,不是绝对的,而是现实的。Java代码是安全的,因为与许多其他语言相比,开发人员用它犯错误的可能性较小。它是安全的,因为Java代码不能直接访问操作系统或硬件,因此Java运行时可以限制Java程序可以做什么和不可以做什么。

Java代码是可移植的。这意味着你可以在一个平台上编译Java代码,并在任何实现了Java虚拟机的平台上运行,而无需重新编译。"一次编写,随处运行"——这个古老的Java口号,在25年后的今天仍然可行。

广泛的可移植性伴随着向后的兼容性是一个非常强大的保证组合,它使开发者相信他们的努力和知识不会迅速过时。 当然,也有一些例外,一些虚拟机由于硬件限制等不同原因,只允许使用Java语言规范的子集。例如,你可以在8kB RAM的微控制器上运行Java代码,但也有一些你必须考虑的限制。

Java代码是可维护的。与C++等语言相比,Java的语法是一个很大的简化;它缺乏许多C++所具有的功能、定制和强大的结构。同时,与脚本语言相比,Java有许多"仪式",乍一看是多余的。在这个意义上,Java试图在复杂性、能力和可读性之间保持平衡,以最大限度地提高代码的长期可维护性。

什么是可维护性?我把它描述为一个普通的熟练的开发人员在现有的代码库(可能是一个旧的代码库)中应用变化所需要的时间,这个变化会导向开发人员的目标,并且不会破坏其他东西。需要的时间越少,可维护性就越强。

在这个意义上,Java是好的。一方面,它强大到足以表达开发人员所需要的许多东西,但同时又不像有些语言那样复杂,人们可以使用极其强大的语言结构创造出美丽的、无法解决的迷宫,而这些迷宫只有其创造者才能理解。另一方面,Java迫使开发者用更多明确的东西写出更多冗长的代码,而不是开发者通常在脚本语言中所做的那样,以增加可读性和易懂性。

Java是迅捷的。如果你试图研究这个主题,你可能会发现几十篇像 "Java比X快 "和 "X比Java快 "这样的文章,其中会有相互矛盾的陈述和结论。如果你尝试自己做实验,你会很容易地构建出Java很慢和Java快得惊人的例子——顺便说一下,你对其他语言也可以这么做。

有一个很好的评论是关于为什么Java在过去被认为是慢的。现在已经有点过时了。例如,最新的Java运行时对字符串的处理比七年前要好得多,同时我也不同意其他一些说法。但一般的结论是,随着每个版本的发布,Java都会得到优化,从而提高其性能,这是真的。还记得本文语法部分的第三个例子吗?

在我的笔记本上,使用Java 8时,平均需要10秒钟,而使用Java 15时,在相同的配置下只需要6秒钟。这是该语言的开发者给我们的重要保证之一。今天,Java对许多任务来说已经足够快了,而且将来会更快。

关于可靠性的最后一件重要事情是这样的。它们已经履行了25年以上,没有理由在未来几年内不履行。

2.4 Java引入现代语言功能的速度很慢

是的。一般来说,速度慢是一件坏事吗?不是。请问你自己,以下是一个问题。如果你在骑自行车,偶尔看到前面有一堵墙,你会选择什么:加速还是减速?我打赌你的选择和我一样。

那么,为什么Java采用功能的速度要比其他一些语言慢呢?因为可靠性,我们在上一节讨论过。这些可靠性就是墙,它迫使功能被仔细讨论、过滤或以某种方式转化,在这里,慢是比急更好的朋友。

对于如何应用Java语言的变化,有一个正式的过程,叫做Java Community Process。这个流程的主要目的是验证提议的功能不会破坏Java的可靠性,而这明显有时不是一个快速的任务。

Java语言的开发者试图在创新和可靠性之间保持平衡,这比"让我们现在就给我们的语言添加这个很酷的功能"的策略要难得多。但从长远来看,它能带来更多的益处,因为可靠意味着信任,这对未来来说是比很酷的功能更有价值的东西。Brian Goetz有一篇关于这个策略和Java整体哲学的精彩演讲,请看一下。

另外,值得一提的是目前的Java发布时间表。每六个月的9月和3月,都会发布一个新的Java版本,该版本完全可以使用,并在接下来的六个月里逐步更新。每三年,有一个这样的版本成为长期支持(LTS)版本,在接下来的三年里逐步更新。

现在,Java 15是最新的版本(译者注:本文写于2020年),Java 11是目前的LTS。下一个LTS版本将是Java 17,计划在2021年9月发布。每个版本都可能包括一些 "预览功能"。这些功能不保证在未来版本中的兼容性。他们的目标是让开发者尝试有争议的创新,并留下有关反馈。如

果一个功能没有被标记为预览版,这意味着它将被完全包含在平台中,并将拥有Java的可靠性。

2.5 生态系统

有些人狭义地理解语言生态系统,将其概念仅仅限制在程序员可以使用的一组库上。我更倾向于从更广泛的角度来考虑生态系统,认为它是处理问题的工具。开发者能用语言解决的问题越多,其生态系统就越广泛。你喜欢哪个意思并不重要。Java有一个巨大的生态系统。

以下列出的一些问题是我以前遇到的个人问题:

  • 我可以在对HTML、JS和CSS一无所知的情况下用纯Java编写网络应用吗?可以
  • 我可以用以毫秒为尺度的TB级的堆来停止世界吗?是的,很容易
  • 我可以用纯Java处理图像和视频,以实现可移植性吗?可以
  • 我可以用Java做深度学习吗?是的
  • 我可以用Java为我在黑色星期五买的那个机器人编程吗?可以
  • 我能找到关于Java的个人愚蠢问题的答案吗?是的

这张清单绝对是个人的,但我非常肯定,在大多数情况下,当问题在编程背景下涉及到Java时,"是 "的实例会压倒 "非"的实例。

在Java生态系统的帮助下,你可以解决许多领域的许多问题,而且,通常你也有多种选择,可以选择如何做。如果你想了解Java生态系统有多庞大,请看一下这个资源。

2.6 启动时间和内存占用

Java代码被编译成一种叫做Java Byte Code译者注:Java字节码)的中间形式,然后在叫做JVM的运行平台上执行。除了抱怨Java的语法,批评者通常还抱怨JVM的内存占用和启动时间。让我们更详细地讨论一下。

JVM是Java虚拟机的缩写,这意味着你的应用程序是在虚拟计算机的沙盒中运行。这种方法有很多好处。开发人员不需要考虑操作系统和硬件的细微差别,应用程序会在其中运行。虚拟沙盒提供了更大的安全能力,因为它不允许应用程序与低级别的东西直接互动。虚拟环境站在你的应用程序之外,可以动态地优化它,以提高在不同情况下的性能,等等。

缺点是,JVM需要额外的资源,包括内存和处理器时间,才能发挥作用,而且它还有一个启动和预热时间。

在通常的使用情况下,标准的Oracle HotSpot JVM引入了几十或几百兆的额外占用空间,并且平均需要几秒钟的启动时间,这取决于应用程序。(JVM本身通常在一秒内启动,但其他一些库由于其内部例程会增加这个时间)。另外,在启动后的最初几秒钟,JVM可能会消耗比平均水平更多的CPU,因为它会识别并编译字节码的"热点"部分,以优化其未来的使用。

在大多数情况下,这些缺点对于许多类型的应用是合理的。但是,在有些情况下,它们不是这样的,而且你想以某种方式交换好处和缺点。那么,你应该怎么做呢?你应该放弃Java而选择其他的东西吗?通常不需要--只需采用另一种适合你的特定任务的运行环境。

例如,考虑一下微服务领域。现代微服务应用程序通常需要最小的内存占用和启动时间,以便有效地填充Kubernetes等容器协调器。为了满足这一需求,Java的开发者创建了GraalVM。它允许开发者从Java代码中创建本地镜像,这些镜像将在几十毫秒的启动时间内运行,并且只有几兆字节的额外内存占用。许多Java网络框架将GraalVM用于微服务领域。Quarkus, Micronaut, Spring, Helidon。

交易的弊端?你失去了可移植性,构建的镜像只能在GraalVM编译的平台上运行。但对于微服务来说,这其实并不重要,因为你的应用程序很可能会在预定义环境的容器中运行。你也可能面临其他一些限制。总之,当你听到Java不适合现代微服务需求时,请记住。这种说法是错误的。

Java并不像许多批评者所说的那样意味着过度使用内存和缓慢的启动时间。内存使用量和启动时间主要取决于用于最终运行一个应用程序的运行时间和它所使用的额外库。从这个意义上说,Java生态系统给你的选择取决于你的需要。

2.7 Java到底是干什么的?

Java可以用于你能想象的一切:API和网络服务器,游戏和多媒体软件,UI和网络应用,物联网和机器人,AR和VR,机器学习和数据流,数据库和云原生微服务,巨大的企业系统和小型软件项目。

是否有任何例外情况?技术上没有,实践上有。Java并不打算成为一种低级别的系统语言,所以用Java创建操作系统的核心或硬件驱动并不是一个好主意。技术上是可以的,但对于这些情况,有比Java更好的工具。

2.8 但我们必须为Java付费,对吗?

不,如果你使用免费的Java发行版,你不需要为Java付费。

Java是开源的,因此任何人都可以建立随时可用的Java发行版,有许多免费的预建发行版,如OpenJDK、AdoptOpenJDK、AWS Coretto、Azul Zulu,以及其他。这些发行版的使用是完全免费的,它们中的任何一个都很可能满足你的要求。如果你想了解更多这方面的信息,请参考这篇文章

2.9 Java的未来

总结一下,Java仍是一个庞然大物。

Java的角色是成为许多领域的核心技术,平衡创新、能力和可维护性,以此可持续地支持它所应用的项目。如果你喜欢尝试很酷的新语言理念,请选择其他技术。

但是,如果你看到一个特定的功能最终进入了Java规范,你可以肯定它的加入不是偶然的机会或者为了赶上潮流,而是深入研究和重大设计努力的结果,以达到其他Java功能所具有的相同水平的可靠性。从这个意义上说,你可以信任Java,而不像市场上许多其他技术。

如果你想知道你应该学习哪种计算机语言作为你的第一种或下一种语言,就请你试一试Java。我相信,Java将伴随我们很长时间。

我敢打赌,还有很多本文遗漏的好的论据,说明为什么Java会长寿,欢迎大家在评论中分享。

往期精选

  1. Java 8 Stream 从入门到进阶——像SQL一样玩转集合
  2. Java8-15的新特性,你知道几个?
  3. 从开源创业之星到造炸弹,最后删库跑路,他经历了什么?
  4. 11个值得掌握的Java代码性能优化技巧
  5. 阿里巴巴的Java开发手册(黄山版)来了

我是翊君,一个喜欢阅读、摄影、写文的半吊子码农,关注我的公众号「野人花园」,一起玩耍吧!