vlambda博客
学习文章列表

Java 二十五载,正在 Kotlin 化!

相比 Groovy、Scala、Clojure 等来说,Kotlin 保留了 Java 所有的优点,消除了 Java 存在的大部分问题。这是它为什么如此受欢迎的主要原因,许多人认为它可以在未来几年取 Java 而代之。


Java 在问世之初的八到十年里没有太多的竞争对手。问世十年后,开始有了一些竞争对手。那么,存在竞争是好是坏呢?

Java 二十五载,正在 Kotlin 化!

作者 | The Bored Dev
译者 | 明明如月,责编 | 沭七
图 | CSDN 下载自东方 IC
出品 | CSDN(ID:CSDNnews)

以下为译文:

有些人会对 Java 略有微词,他们批判用 Java 写代码非常冗长,很多情况下不得不编写没有必要的样板方法。我一直都非常喜欢 Java,对于这些指控我不敢苟同。

的确,Java 的冗长和大量的样板方法通常会令人生厌。然而,我们生活的世界并非完美。在大多数情况下,我们不得不两害相权取其轻。我们都知道 Java 并不完美,但是为什么之前没有采取任何措施来解决这些问题呢?

我个人看来,Java 之所以较长时间内没有特别大的改进唯一原因是没有足够强劲的竞争对手。在缺乏强有力的竞争对手的情况下,Sun 和 Oracle 先后做出的巨大努力让 Java 占据了编程语言市场的主导地位。

Java 提供了强类型安全性,非常严谨的语言结构,成为很多大型项目的首选编程语言。使用 Java,项目就会变得比较可控。此外,Java 通过虚拟机实现跨平台是其一大特色和优势。由于使用著名的 JIT 编译器,借助它自动性能优化的能力,可以极大程度上降低糟糕代码的影响,这是很多人选择使用 Java 编程语言的原因。

然而事情悄悄发生了变化,许多新的编程语言可以在 Java 虚拟机(JVM) 上运行,而且这些编程语言还解决和 Java 中存在的很多问题。不仅如此,这些编程语言入门也相对容易一些。

下面我先简明扼要地介绍下  JVM 编程语言的发展史。


Java 二十五载,正在 Kotlin 化!

JVM 编程语言的发展史


在开始的时候我要先声明一下,由于很多 JVM 编程语言没有引起广泛关注和广泛使用,本文略过了这些编程语言。接下来,让我们快速了解 JVM 语言的历史。

很显然,我们必须从 Java 开始介绍,因为 Java 是 JVM 世界中最古老、最流行的编程语言。

Java 语言是 1995 年 5 月诞生,1996 年 1 月正式发布,已经有了 25 年的历史。最初,Java 是一种纯命令式的编程语言,遵循纯粹的面向对象程序设计风格的强类型语言。它的语法在某些方面与 C + + 和 C 语言相似。它可以看作是 C++ 和 C 语言的改进版本,因为用 Java 编写代码比用 C 或 C++ 容易得多。不过 Java 代码冗长是被很多人批评的主要原因。

第二种 JVM 语言是 Groovy。尽管它的第一个官方和标准化版本 1.0 直到 2007 年 1 月才发布,它在 2003 年就已经问世。Groovy 具有脚本语言的优势。Groovy 是一种动态类型语言,因此到运行时才进行类型检查,这也是一些开发人员不喜欢 Groovy 的原因之一。如果你使用 Groovy 编写代码,编译时看起来非常正确,某些错误直到在运行时才能被发现。

Java 二十五载,正在 Kotlin 化!

图源:Groovy 官网

接下来是一种流行了多年的编程语言。可能你已经猜到了,它就是 Scala。Scala 在 2004 年正式发布,它给 JVM 世界带来了一个新的编程模型,包括函数式编程和声明式方法。Scala 是第一个引入不变性概念的编程语言,这在 Java 语言的演化中扮演着重要的角色。但是它语法复杂和可读性不高,导致很多人不喜欢它。

Java 二十五载,正在 Kotlin 化!

图源:Scala 官网

在 JVM 世界中出现的下一种语言是 Clojure,它是一种纯粹的函数式语言。虽然它早 2007 年就已经问世,但是最近才开始流行。Clojure 是一种基于 Lisp 的语言,其特点是简单和使用纯函数。不过它也有很多缺点如:动态类型(与 Groovy 相同)和陡峭的学习曲线,而且语法与其他 JVM 语言也完全不同。

如果你对 Clojure 感兴趣,那么你应该阅读以下几本好书: 《The Joy of Clojure[1]》和《Programming Clojure: 3rd Edition[2]》。

Kotlin 虽然放在最后介绍,但它却非常重要,Kotlin 自 2016 年 2 月首次发布时起,它就一直非常流行。它是由 JetBrains 公司设计,它的设计目标非常明确,消除 Java 的所有问题。在设计的过程中,它保留了 Java 所有的优点,消除了 Java 存在的大部分问题。这是它为什么如此受欢迎的主要原因,许多人认为它可以在未来几年取 Java 而代之。

Java 二十五载,正在 Kotlin 化!

图源:Kotlin 官网

如果你想了解更多 Kotlin 的信息(推荐你学习下 Kotlin,因为它是一种很棒的编程语言),推荐你阅读《Kotlin in Action》一书,这是一本面向 Kotlin 初学者的经典图书。

上面介绍了几个最重要的 JVM 编程语言。但是还有一些不太受欢迎的 JVM 编程语言,如 Jython、 JRuby、 Ceylon、 Fantom 等等。你可以在维基百科中查看 JVM 编程语言列表[3]

值得一提的是,Java 在问世之初的八到十年里没有太多的竞争对手。问世十年后,开始有了一些竞争对手。那么,存在竞争是好是坏呢?


Java 二十五载,正在 Kotlin 化!

竞争加剧的好处


正如我们之前提到的,Java 在早期并没有太大改变。尽管 Java 还远远不够完美,但是它使用非常广泛,非常流行。

后来出现了新的竞争对手,更多的现代语言带来了新的特性,解决了 Java 开发人员长期以来一直面临的一些问题。

如 Scala 语言,自 2009 年以来,它受欢迎程度一直在上升。开发人员对这种新的函数式风格非常欢迎,这种风格使他们能够更加灵活地编写并行代码。我们可以在下面的 Google Trends 中看到这种趋势:

Java 二十五载,正在 Kotlin 化!

那么 Oracle 对这一新趋势的反应如何?2014 年为 Java 添加了 Lambda 表达式和 Stream 特性。这一举措是 Java 在击败 Scala 方面迈出了最大的一步。目前软件开发行业工作的人都能感受到 Scala 近年来涨势减缓。

在 JVM 世界中拥有更多竞争对手的另一个好处是 JIT 编译器和 JVM 本身不断改进。现在有更多的人对 JVM 本身的优化和 Java 的性能改进非常感兴趣。所以从这方面来看,竞争的确是一件好事!

最后一个出现在竞技场上的竞争对手是 Kotlin,而 Kotlin 一直非常重要,因为在某种程度上,它为 Oracle 指明了方向。Kotlin 已经证明,在保留 Java 的优点的前提下,创建一种更简洁的语言来加快编码速度的可行性。

如果我们看看 Google Trends,就会发现 Kotlin 在过去几年里有多么受欢迎:

Java 二十五载,正在 Kotlin 化!

从上图可以看出 Kotlin 很快就流行起来了。然而,在最近几年,它似乎已经趋于稳定。

Oracle 也注意到了业内对 Kotlin 的反应。如果你看一下 JDK 15 的发行说明,你会发现 Java 的一些新特性都是借鉴自 Kotlin。示例包括新的 Java 记录、新的文本块(带有三重引号的多行字符串)和新的 switch 语句(或多或少借鉴了 Kotlin 的 when 语句)。

我称上面的这种现象为「Java 的 Kotlin 化」。Kotlin 变成了 Java 强有力的竞争对手,Kotlin 也为 Java 指明了发展方向。在我看来,Kotlin 是我见过的唯一一种能够超越 Java 成为行业领导者的语言。


Java 二十五载,正在 Kotlin 化!

Kotlin 化的 Java


即将发布的 Java 语言新特性在改进可读性和简化代码方面做出了很大改进。我们可以看到,这些新特性和 Kotlin 得到很多特性非常类似。

下面只是功能预览,这意味着,如果你在 JDK 14 或 JDK 15 发布时安装它们,默认情况下无法使用它们。

Java 特性预览是在一个发行版中引入的新特性,但默认是禁用的。它们被包含在发行版中只是为了收集来自开发人员社区的反馈,因此还有变更的可能性。这也是为啥不推荐在生产环境中使用这些特性的重要原因。

如果想要在编译时启用它们,您必须执行以下操作:

javac --enable-preview --release 14

如果你想在运行时启用它们,你必须运行以下命令:

java --enable-preview YourClass

当然,你也可以在 IDE 中启用这些特性,但是请注意不要在所有新项目中默认启用预览!

接下来让我们来看看这些变化。这些变化可能对我们未来使用 Java 新版本编码产生巨大影响。

  • Java 记录

Java 记录(records)是我们许多人长期以来一直期盼的一个特性。我想你经常会遇到不得不实现 toString、hashCode、equals 和 getter 方法的情况(我假设你已经不再使用 setter 方法,实际上不应该使用)。

Kotlin 提供了数据类[4]来解决这个问题,Java 也打算通过发布记录类来实现相同的功能,Scala 的 case 类也有这个功能。

这些类的主要用途是在对象中保存不可变数据。让我们通过一个例子来看看它在 Java 中的用法。如果我们想要实例化和比较我们的 Employee 类,就需要写下面的代码:

package com.theboreddev.java14;

import java.util.Objects;

public class Employee {
    private final String firstName;
    private final String surname;
    private final int age;
    private final Address address;
    private final double salary;

    public Employee(String firstName, String surname, int age, Address address, double salary) {
        this.firstName = firstName;
        this.surname = surname;
        this.age = age;
        this.address = address;
        this.salary = salary;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getSurname() {
        return surname;
    }

    public int getAge() {
        return age;
    }

    public Address getAddress() {
        return address;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age &&
                Double.compare(employee.salary, salary) == 0 &&
                Objects.equals(firstName, employee.firstName) &&
                Objects.equals(surname, employee.surname) &&
                Objects.equals(address, employee.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, surname, age, address, salary);
    }

    @Override
    public String toString() {
        return "Employee{" +
                "firstName='" + firstName + '\'' +
                ", surname='" + surname + '\'' +
                ", age=" + age +
                ", address=" + address +
                ", salary=" + salary +
                '}';
    }
}

上面类中还包含 Address 成员属性,它的类结构如下:

import java.util.Objects;

public class Address {
    private final String firstLine;
    private final String secondLine;
    private final String postCode;

    public Address(String firstLine, String secondLine, String postCode) {
        this.firstLine = firstLine;
        this.secondLine = secondLine;
        this.postCode = postCode;
    }

    public String getFirstLine() {
        return firstLine;
    }

    public String getSecondLine() {
        return secondLine;
    }

    public String getPostCode() {
        return postCode;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(firstLine, address.firstLine) &&
                Objects.equals(secondLine, address.secondLine) &&
                Objects.equals(postCode, address.postCode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstLine, secondLine, postCode);
    }

    @Override
    public String toString() {
        return "Address{" +
                "firstLine='" + firstLine + '\'' +
                ", secondLine='" + secondLine + '\'' +
                ", postCode='" + postCode + '\'' +
                '}';
    }
}

相对于要实现的简单功能来说,这些代码太长了,难道不是吗?

现在让我们看看使用 Java 的记录后的代码:

public record EmployeeRecord(String firstName, String surname, int age, AddressRecord address, double salary) {
}

Address 类:

public record AddressRecord(String firstLine, String secondLine, String postCode) {
}

这和我们前面冗长的代码的功能等价。通过对比你可以发现,使用记录这一特性,代码减少了很多而且写起来更简单。

  • 改进的 switch 语句

Java 改进的 switch 语句解决了在 Java 中使用 switch 语句的一些固有问题。其实之前的版本应该始终避免使用 switch 语句,因为非常容易出错并容易导致代码重复。case 很容易遗漏,使用改进的 switch 语句就不存在这个问题。因为如果 switch 语句不覆盖传递给 switch 的类型域的范围,那么就无法编译通过。

接下来我们将用一个例子来解释这个问题。

我们需要先创建一个 DayOfTheWeek 枚举类:

public enum DayOfTheWeek {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

如果我们想编写星期几在一周的位置,使用 Java 11 可以这么写:

final DayOfTheWeek dayOfTheWeek = DayOfTheWeek.THURSDAY;

        int position = 0;

        switch (dayOfTheWeek) {
            case MONDAY:
                position = 1;
                break;
            case TUESDAY:
                position = 2;
                break;
            case WEDNESDAY:
                position = 3;
                break;
            case THURSDAY:
                position = 4;
                break;
            case FRIDAY:
                position = 5;
                break;
            case SATURDAY:
                position = 6;
                break;
            case SUNDAY:
                position = 7;
                break;
        }

        System.out.println("Day " + dayOfTheWeek + " is in position " + position + " of the week");

像上面这样使用 switch 语句,我们必须引入一个变量,如果我们漏写了一周的某一天,我们的代码依然可以完美地编译通过。这是 switch 语句的问题之一,即非常容易出错。

那么 Java 14 是如何改善这种情况的呢?

final DayOfTheWeek dayOfTheWeek = DayOfTheWeek.THURSDAY;

        int position = switch (dayOfTheWeek) {
            case MONDAY -> 1;
            case TUESDAY -> 2;
            case WEDNESDAY -> 3;
            case THURSDAY -> 4;
            case FRIDAY -> 5;
            case SATURDAY -> 6;
            case SUNDAY -> 7;
        };

        System.out.println("Day " + dayOfTheWeek + " is in position " + position + " of the week");

你将很快看到,改进的 switch 语句可以用作表达式,而不仅仅是语句。

这样做更简洁而且更有表现力,足以吸引大多数人使用这一特性。如果我们不能涵盖 switch 中的所有 case,那么 swtich 语句将不能编译。将报如下的错误:

Error:(924) java: the switch expression does not cover all possible input values

使用改进的 switch 特性,就不会在 switch 中遗漏 case 条件。

这非常棒,难道不是吗?

这与 Kotlin 的 when 语句非常相似,你可以在此文[5]中了解相关用法。

下面我们介绍文本块特性。

  • 文本块

你有没有抱怨过在 Java 中给一个变量分配一大块 JSON 是多么的难以处理?Java 将引入多行字符串,你可以通过将它们包装在三引号之间来定义它们。当这个特性正式发布时,在多行中定义长字符串就容易多了。

让我们来看看这两种模式之间的区别。目前,如果我们想在一个变量中存储一个格式化的 JSON 字符串,就会像下面这个一样丑陋:

inal String text = "{\"widget\": {\n" +
                "    \"debug\": \"on\",\n" +
                "    \"window\": {\n" +
                "        \"title\": \"Sample Konfabulator Widget\",\n" +
                "        \"name\": \"main_window\",\n" +
                "        \"width\": 500,\n" +
                "        \"height\": 500\n" +
                "    },\n" +
                "    \"image\": { \n" +
                "        \"src\": \"Images/Sun.png\",\n" +
                "        \"name\": \"sun1\",\n" +
                "        \"hOffset\": 250,\n" +
                "        \"vOffset\": 250,\n" +
                "        \"alignment\": \"center\"\n" +
                "    },\n" +
                "    \"text\": {\n" +
                "        \"data\": \"Click Here\",\n" +
                "        \"size\": 36,\n" +
                "        \"style\": \"bold\",\n" +
                "        \"name\": \"text1\",\n" +
                "        \"hOffset\": 250,\n" +
                "        \"vOffset\": 100,\n" +
                "        \"alignment\": \"center\",\n" +
                "        \"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n" +
                "    }\n" +
                "}} ";

当新的文本块发布时,它会像下面这样简单干净:

final String multiLineText = """
                {"
widget": {
                    "
debug": "on",
                    "
window": {
                        "
title": "Sample Konfabulator Widget",
                        "
name": "main_window",
                        "
width": 500,
                        "
height": 500
                    },
                    "
image": {\s
                        "
src": "Images/Sun.png",
                        "
name": "sun1",
                        "
hOffset": 250,
                        "
vOffset": 250,
                        "
alignment": "center"
                    },
                    "
text": {
                        "
data": "Click Here",
                        "
size": 36,
                        "
style": "bold",
                        "
name": "text1",
                        "
hOffset": 250,
                        "
vOffset": 100,
                        "
alignment": "center",
                        "
onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
                    }
                }}
                """
;

我觉得这样好多了,你同意吗?这也是 Kotlin 支持的,你可以在它的类型定义中找到。

所以我们看到 Java 从它的竞争对手 Kotlin 那里学到了许多解决自身问题的方案。我们不知道这一次 Oracle 应对 Kotlin 崛起的动作是不是有点晚了。就我个人而言,虽然这些变化是由它的竞争对手激发的,而且有些晚,但是我仍然认为 Java 正在稳步前进。

如前所述,如果这篇文章激发了您学习 Kotlin 语言的兴趣,我建议您阅读《Kotlin in Action》 ,这是一本非常适合Java 开发人员初学 Kotlin 的书。


Java 二十五载,正在 Kotlin 化!

总结


我认为竞争对 Java 的发展来说是一件好事。如果没有竞争,Java 很容易满足于现有的成就而裹足不前。此外,Java 的竞争对手探索了多种编程方式,避免一直停留在陈旧的固有的编码方式上。

Java 最近的变化以及即将问世的新特性和改进,使得 Java 比任何时候都强大。作为一个适应时代发展要求,敢于走出舒适区的编程语言, Java 将会迎来新的发展机遇。

[1] https://www.manning.com/books/the-joy-of-clojure-second-edition

[2] https://www.amazon.com/dp/1680502468/

[3] https://en.wikipedia.org/wiki/List_of_JVM_languages

[4] https://kotlinlang.org/docs/reference/data-classes.html

[5] https://kotlinlang.org/docs/reference/control-flow.html

英文:A New Future for Java

链接:https://medium.com/better-programming/a-new-future-for-java-b10a6789f962

译者:明明如月,知名互联网公司 Java 高级开发工程师,CSDN 博客专家。

本文为 CSDN 翻译,转载请注明来源出处。

【END】

Java 二十五载,正在 Kotlin 化!

    
      
      
    
更多精彩推荐
     
       
       
     
Java 二十五载,正在 Kotlin 化!
点分享
点点赞
点在看