1. 前言
开篇先开宗明义:虽然时代发展很快,但是没有那么多新技术,新技术都是在老技术的基础上,根据当时环境的需要进行的改进。新技术一定是解决了某个老技术没有解决的问题。
在进入正题之前,我们先看看这几个语言在编程语言排行榜上的占比,数据来源Tiobe 2024年4月份榜单。
Java,第4名,占比8.94% ,比去年同期下降1位。
Kotlin,第18名,占比1.05%,比去年同期上涨38位。
Scala,第33名,占比0.46%。
Clojure,第64名。
Groovy,第71名。
上面的榜单只代表这几个编程语音的流行占比。下面我们看另一份榜单。
Stack Overflow 发布的2023 Developer Survey。
Java,期待16.53%,钦佩44.11%
kotlin,期待12.10%,钦佩60.77%
Scala,期待3.18%,钦佩52.27%
Clojure,期待2.20%,钦佩68.51%
Groovy ,期待1.41%,钦佩29.97%
这份榜单中可以看到Clojure、kotlin是明显更受欢迎的。
我们再看一下收入排行榜,年薪中位数:
Clojure,96,381美元
Scala 96,381美元
Groovy 86,271美元
Kotlin 78,207美元
Java 72,701美元
收入方面Clojure和Scala一骑绝尘。
2.Java和Scala、Groovy、Clojure 、Kotlin的关系
有那么多的编程语言,我们为什么要对比这些,这几个语言之间有什么关系?
Java和Scala、Groovy、Clojure 、Kotlin语言最主要的关系就是他们都是运行在JVM(Java 虚拟机)的语言。
JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器。
对于不同的平台,有不同的虚拟机。
Java虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”。如下图:
Scala、Groovy、Clojure 、Kotlin语言也可以编译成字节码文件在JVM中运行。
运行在JVM上的优势是能够利用 JVM 的所有特性,包括跨平台能力、内存管理、垃圾回收和安全性等。同时,也能够直接调用 Java 的类库和接口,使得它在访问庞大的 Java 生态系统时没有任何障碍。
当然随着时间的发展,一些语言也可以编译成其他形式运行在其他的平台,比如Kotlin 可以编译为 JavaScript 或使用 Kotlin/Native 直接编译为机器码,以运行在没有 JVM 的平台上。Clojure 也能编译为 JavaScript,通过 ClojureScript 支持在浏览器端运行。但是他们由于最开始是运行在JVM平台上,所以被大家拿出来比较。
3. 从java到Scala、Groovy、Clojure 、Kotlin的历史沿革
3.1 Java语言
出现时间:1990
开发者:SUN公司詹姆斯·高斯林主导开发
当前维护者:Oracle 公司
开发的目的:最开始被戏称为C++--,Java 设计时去掉了 C++ 中一些被认为复杂或容易引发错误的特性(如指针直接操作、多重继承等),旨在提供一个更简洁、更安全、跨平台的编程环境。初衷是简化电视机顶盒控制软件的开发,后扩展到企业级应用、移动开发等。
优势领域:Java 在企业级应用、Android 移动开发、大型系统开发等领域保持着明显的优势。这得益于其稳定的性能、跨平台能力、成熟的生态系统以及广泛的社区支持。Java 拥有庞大的标准库、丰富的开发工具和框架,这些使得 Java 在构建可靠、可扩展的大型应用程序方面非常受欢迎。
学习难度:中
3.2 Scala语言
出现时间:2003
开发者:Martin Odersky 和他在洛桑联邦理工学院的团队
当前维护者:Lightbend 公司
开发目的:将面向对象和函数式编程的特性结合起来,提供一种“可伸缩的语言”,适用于从小型脚本到大型系统的开发,提升代码表达力和开发效率,特别适用于并发编程和大数据处理领域。
学习难度:高。Scala 融合了面向对象和函数式编程的特性,这让它成为一门功能强大但学习曲线较陡的语言。对于那些已经熟悉 Java 的开发者来说,学习 Scala 的面向对象部分可能相对容易,但完全掌握其函数式编程特性可能需要更多的时间。
解决Java痛点:解决最初Java没有函数式编程,比Java更擅长大数据处理。即使 Java 现在支持函数式编程,Scala 仍有其独特优势。Scala 提供了更彻底的函数式编程支持和更丰富的语言特性,如模式匹配、案例类、隐式转换等,这些特性使得编写复杂的数据处理逻辑更加简洁和表达力更强。Scala 在类型推断、并发编程模型(如 Akka)上也提供了更先进的支持。因此,尽管 Java 的更新增加了函数式编程能力,Scala 依然在许多方面提供了更优雅的解决方案,特别是在需要高度函数式编程和并发处理的应用场景中。其中Spark和Kafka就是其优秀的实现。
下面举个简单的例子,实现读取文本文件,过滤出包含“important”的行,然后对这些行中的单词进行计数,并将结果保存到另一个文件中。
val data = sparkContext.textFile("path/to/data.txt")
val filteredData = data.filter(line => line.contains("important"))
val wordCounts = filteredData.flatMap(line => line.split(" "))
.map(word => (word, 1))
.reduceByKey(_ + _)
wordCounts.saveAsTextFile("path/to/output")
在 Java 7 中,使用了匿名类来实现函数式接口,这使得代码相对冗长:
public class SparkApplication {
public static void main(String[] args) {
JavaSparkContext sc = new JavaSparkContext("local", "SparkApp");
JavaRDD<String> data = sc.textFile("path/to/data.txt");
JavaRDD<String> filteredData = data.filter(new Function<String, Boolean>() {
public Boolean call(String s) { return s.contains("important"); }
});
JavaRDD<String> words = filteredData.flatMap(new FlatMapFunction<String, String>() {
public Iterable<String> call(String s) { return Arrays.asList(s.split(" "));
});
JavaRDD<Tuple2<String, Integer>> wordPairs = words.map(new PairFunction<String, String, Integer>() {
public Tuple2<String, Integer> call(String s) { return new Tuple2<>(s, 1); }
});
JavaRDD<Tuple2<String, Integer>> wordCounts = wordPairs.reduceByKey(new Function2<Integer, Integer, Integer>() {
public Integer call(Integer a, Integer b) { return a + b; }
});
wordCounts.saveAsTextFile("path/to/output");
}
}
Java 8 示例
Java 8 引入了 Lambda 表达式,极大简化了函数式接口的实现。在 Spark 示例中,使用 Lambda 表达式替代了冗长的匿名类,使代码更加简洁和易于维护。:
public class SparkApplication {
public static void main(String[] args) {
JavaSparkContext sc = new JavaSparkContext("local", "SparkApp");
JavaRDD<String> data = sc.textFile("path/to/data.txt");
JavaRDD<String> filteredData = data.filter(s -> s.contains("important"));
JavaPairRDD<String, Integer> wordCounts = filteredData.flatMap(s -> Arrays.asList(s.split(" ")).iterator())
.mapToPair(s -> new Tuple2<>(s, 1))
.reduceByKey((a, b) -> a + b);
wordCounts.saveAsTextFile("path/to/output");
}
}
3.3 Groovy语言
出现时间:2003
开发者:James Strachan
当前维护者:Apache 社区
开发目的:增强 Java 的生产力,通过提供一个更加动态、更加简洁的语法来简化 Java 开发者的日常编程任务。提高脚本编写效率,特别在构建脚本、测试和Web应用开发中受欢迎。
学习难度:低。Groovy 的设计目标之一是提高开发效率和简化 Java 的语法,因此它对于 Java 开发者来说相对容易学习。它的语法灵活且富有表现力,但这也可能导致代码的不一致性和维护上的困难。
解决Java痛点:Groovy 解决了 Java 的语法冗长和静态性的痛点。它提供了更简洁的语法,动态类型,以及对脚本语言特性的支持,使得开发过程更加快速和灵活。此外,Groovy 还增强了 Java 的元编程能力,使得编写 DSL(领域特定语言)和测试代码变得更加简单。
以 Groovy 和 Java 在编写数据库查询脚本为例。在 Java 中,你可能需要编写多行代码来创建数据库连接、构建查询语句、执行查询并处理结果。这不仅包括冗长的异常处理,还需要管理资源释放等。而在 Groovy 中,可以利用 Groovy 的 SQL 支持和闭包(Closure),以极其简洁的方式完成相同的任务,通常只需几行代码。Groovy 自动处理资源管理和异常,让开发者能够专注于业务逻辑。这体现了 Groovy 在简化 Java 开发中的一些常见痛点方面的优势。
在 Java 中,执行数据库查询可能像这样:
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "pass");
try {
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM my_table");
while (rs.next()) {
String myField = rs.getString("myField");
System.out.println(myField);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
conn.close();
}
而在 Groovy 中,可以用更简洁的方式实现相同的操作:
def sql = Sql.newInstance("jdbc:mysql://localhost:3306/mydb", "user", "pass", "com.mysql.jdbc.Driver")
sql.eachRow("SELECT * FROM my_table") { row ->
println row.myField
}
Groovy 示例展示了它如何简化资源管理(无需显式关闭连接)和利用闭包来处理结果集,这使得代码更简洁、更易读。
增强 Java 的元编程能力意味着 Groovy 允许在运行时更改程序的行为,包括添加或修改类和方法。这种灵活性让开发者可以编写更具表达性和动态性的代码,例如创建 DSL 或进行灵活的测试。简单来说,元编程能让你的代码在运行时根据需要去"编写"或改变其他代码,这在 Java 中是受限的。
比如,Groovy 允许你在运行时向现有类添加新方法或属性,这种能力在 Java 中不直接可用。举个例子,如果你想给 String 类添加一个新方法 reverseString,在 Groovy 中你可以这样做:
String.metaClass.reverseString = { -> delegate.reverse() }
println "hello".reverseString() // 输出 olleh
这里通过 metaClass 对象,我们向 String 类动态添加了一个 reverseString 方法,然后就能像调用原生方法一样调用它。这种动态修改类的行为的能力是 Groovy 元编程的一大特点。
成熟的Groovy DSL应用中,Gradle是一个杰出的例子。Gradle是一个基于Groovy的构建自动化工具,它的构建脚本就使用了Groovy DSL。这使得Gradle的构建配置不仅功能强大,而且易于理解和维护,大大简化了Java、Groovy、Kotlin等语言项目的构建过程。
Groovy 和 Java 之间存在互补关系。Groovy 作为一种动态语言,提供了比 Java 更灵活的语法和更强大的脚本能力,这使得它在编写快速脚本、原型设计和某些类型的 DSL 开发中表现出色。同时,Groovy 完全兼容 Java,可以无缝地调用 Java 类库和框架,这意味着开发者可以利用 Java 的稳定性和成熟的生态系统,同时享受 Groovy 带来的简洁和灵活性。这种互补性使得两者可以在不同的场景下发挥各自的优势。
3.4 Clojure语言
出现时间:2007
开发者:Rich Hickey(美国)
当前维护者:创始人 Rich Hickey 及其开源社区维护
开发目的:在JVM上运行Lisp。现代 Lisp 方言,强调函数式编程和不可变数据结构,旨在简化并发编程,适用于数据分析和Web开发。
学习难度:极高。作为一种现代 Lisp 方言,Clojure 强调不可变性和函数式编程。它的语法和编程范式与传统的面向对象语言如 Java 大不相同,可能需要时间来适应。
解决Java痛点:首先关于Clojure,最多的一句话就是他是运行在JVM上的 Lisp 方言。所以他不是为了解决Java什么问题来的,而是利用了JVM的特点去推Lisp。Lisp是一种以表达性和功能强大著称的编程语言,但人们通常认为它不太适合应用于一般情况,而Clojure的出现彻底改变了这一现状。如今,在任何具备 Java 虚拟机的地方,您都可以利用 Lisp 的强大功能。
下面给大家贴一张图,让大家了解下Lisp和Java他们的关系。
我们下面举个例子,假设我们想要并行地处理一个数字列表,对每个数字加一。
在 Java 中,你可能会使用并行流(Java 8 引入的功能)来实现这一功能:
public class ParallelIncrement {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> incrementedNumbers = numbers.parallelStream()
.map(n -> n + 1)
.collect(Collectors.toList());
System.out.println(incrementedNumbers);
}
}
这里,我们使用了 parallelStream() 来创建一个并行流,然后通过 map 操作将每个数字加一,最后收集结果到一个新的列表。
在 Clojure 中,我们可以使用 pmap 函数来实现类似的并行处理:
(def numbers [1 2 3 4 5])
(def incremented-numbers
(pmap #(inc %) numbers))
(println incremented-numbers)
在这个 Clojure 示例中,pmap 用于并行地映射函数 inc(增加函数,等同于 #+1)到列表 numbers 上。这种方式非常简洁,而且直观地表达了操作的并行性质。
Clojure 的示例展示了纯函数式编程风格,而 Java 仍然保留了其面向对象的特性。
3.5 Kotlin语言
出现时间:2011
开发者:JetBrains 公司的开发团队
当前维护者:JetBrains 公司
开发目的::兼容 Java,同时解决 Java 语言中的一些常见问题,提供比Java更简洁、更安全的编程语言,最终完全替代Java。
学习难度:中。Kotlin 旨在与 Java 完全兼容,同时引入了更现代的语言特性。它的语法清晰、简洁,去掉了许多 Java 中的冗余元素,使得学习起来相对容易,特别是对现有 Java 开发者。
解决Java痛点:Kotlin 设计为与 Java 完全互操作,意味着它可以调用 Java 代码,Java 代码也可以调用 Kotlin 编写的组件。由于其现代化的语言特性和更简洁的语法,Kotlin 在许多方面提供了改进,特别是在 Android 应用开发和后端服务开发中。考虑到这些优势,Kotlin 确实有潜力在许多项目中替代 Java。特别是在新的领域,比如Android的开发,Kotlin 可能是更优的选择,因为它提供了更好的开发效率和语言特性。但对于维护大量现有的 Java 代码库,完全迁移到 Kotlin 可能需要额外的时间和资源。而且Kotlin 得到了 JetBrains 和 Google强大的支持。
Kotlin 提供了许多现代化的特性,使其在某些方面优于 Java。一个明显的例子是 空安全性(null safety)的处理。
在 Java 中,空指针异常(NullPointerException)是最常见的运行时错误之一,经常因为开发者未能正确处理可能为 null 的对象。例如,下面是一个 Java 方法,它可能因为未检查 null 而抛出异常:
public String getUpperCase(String input) {
return input.toUpperCase();
}
如果 input 为 null,这段代码将抛出 NullPointerException。
相比之下,Kotlin 在语言层面上引入了空安全性设计。在 Kotlin 中,默认情况下,所有类型都是非空的,如果你尝试将 null 赋值给一个非空类型的变量,这会在编译时就报错。要允许变量为空,你需要显式声明它可以为空,通过添加 ? 到类型声明后:
fun getUpperCase(input: String?): String? {
return input?.toUpperCase()
}
在这个 Kotlin 版本中,input 是一个可以为 null 的 String 类型(String?)。使用安全调用操作符 ?.,这意味着 toUpperCase() 方法只在 input 不为 null 时调用,否则返回 null。这避免了运行时 NullPointerException,提高了代码的安全性和可靠性。
这个只是一个方面,还有很多对Java的修修补补。比如扩展函数、协程支持、类型推断等等。有兴趣的可以自己去看一下。
4.总结
综上所述,如果是解决Java痛点的话,一句话简述:
Scala:解决了Java不能函数式编程。
Groovy:解决了Java不是动态语言。
Clojure:解决了JVM上不能跑Lisp。
Kotlin: 解决了Java很多问题。为了替代Java。
短期来看,Kotlin的势头很好,会吃掉Java越来越多的份额。Scala和Clojure属于高手进阶。Groovy属于工作辅助。
但是长期的话,引用《七十年编程语言发展漫谈,你用过哪些?》这个文章中的一段话:
我们要回到编程语言的初心: 什么是编程语言?
编程语言是被标准化的,用来向计算机发出指令,让程序员利用计算机能力的工具。
简而言之, 编程语言只是我们利用计算机能力的工具,小白用户能理解按钮,图片,短视频;计算机能理解汇编指令,这时需要一群聪明的人把用户需求转换为机器代码,这群人 用编程语言让这项工作变得简单;二十年后我们还需要一群人专门设计交互、视觉、框架、代码来架起小白用户使用计算机的桥梁吗?到那时的人们也许分不清 AI 和计算机的区别了, 到那时利用计算机的能力也许就像我们现在跟同事说话交流一样简单。
也许你会觉得 AI 也需要编程语言去实现,以方便后续的维护和迭代,但是也许那时 AI 已经具备自己维护自己代码的能力了,那时也许人类已经看不懂什么是 C++、Python,只有 AI 懂了,甚至 AI 自己发明新的编程语言编写了下一代的 AI(AI 可能已经觉得可读性是个包袱了,直接写机器代码不是更爽?)。就如第一个 C 语言编译器是用汇编写的,后面 C 语言编译器是用 C 语言写的一样。我们前面讨论的大部分的编程语言注定回到了历史的垃圾桶里,本来无一物,何处惹尘埃。
编程语言将在历史的发展中失去了作用和价值,成为未来博物馆中一个陈列的展品,向世人展示人类过去经历的苦难。
参考资料:
评论(0)