写点什么

JVM 语言比较研究:Java、Kotlin 和 Scala 的利与弊

  • 2023-10-02
    北京
  • 本文字数:5634 字

    阅读完需:约 18 分钟

大小:1.86M时长:10:50
JVM语言比较研究:Java、Kotlin 和Scala的利与弊

在编程世界中,下划线( '_ ')是一个用途广泛的字符。它通常被称为语法糖,因为它简化了代码,使其更加简洁。

 

本文将探讨下划线在三种流行编程语言中的使用:Java、Kotlin 和 Scala。

 

Java:使用 JEP443 的未命名模式和变量

 

随着 JEP443 的引入,不断发展的 Java 语言在增强代码可读性和可维护性方面又迈出了重要的一步。这个名为“未命名模式和变量(预览版本)”的提案已经从 JDK21 的 Targeted 状态提成到 Completed 状态。

 

该 JEP 旨在通过未命名的模式和未命名的变量来增强语言,未命名模式匹配记录组件而无需声明组件的名称或类型,未命名的变量可以被初始化但不使用。

 

这两者都是用下划线字符表示的,如 r instanceof _(int x, int y) and r instanceof _

 

未命名模式

未命名模式旨在简化数据处理,尤其是在处理记录类时。它们允许开发人员在模式匹配中省略记录组件的类型和名称,这可以显著地提高代码的可读性。

 

考虑如下的代码片段:

 

if (r instanceof ColoredPoint(Point p, Color c)) {    // ...}
复制代码

 

如果 if 块中不需要 Color c 组件,则将其包含在模式中可能会很徒劳并且也不清楚。使用 JEP 443,开发人员可以简单地省略不必要的组件,从而生成更干净、更可读的代码:

 

if (r instanceof ColoredPoint(Point p, _)) {    // ...}
复制代码

 

在只需记录类的某些组件的嵌套模式匹配场景中,这一特性特别有用。例如,考虑一个包含 PointColor 的记录类 ColoredPoint 。如果只需要点的 x 坐标,可以使用未命名模式来省略 yColor 组件:

 

if (r instanceof ColoredPoint(Point(int x, _), _)) {    // ...}
复制代码

 

未命名变量

当必须声明变量,但又不使用其值时,未命名变量会非常有用。这在循环、try-with-resources 语句、catch 块和 lambda 表达式中很常见。

 

例如,考虑以下的循环:

 

for (Order order : orders) {    if (total < limit) total++;}
复制代码

 

在本例中,在循环中没有使用 order 变量。使用 JEP 443,开发人员可以用下划线替换未使用的变量,使代码更加简洁明了:

 

for (_ : orders) {    if (total < limit) total++;}
复制代码

 

switch 语句中,未命名变量也很有用,在这种语句中,对多种情况执行相同的操作,并且不使用变量。例如:

 

switch (b) {    case Box(RedBall _), Box(BlueBall _) -> processBox(b);    case Box(GreenBall _) -> stopProcessing();    case Box(_) -> pickAnotherBox();}
复制代码

 

在本例中,前两中情况使用未命名的模式变量,因为它们的右侧没有使用 Box 的组件。第三种情况使用未命名模式来匹配具有空(null)的 Box 组件。

 

启用预览特性

 

未命名模式和变量是预览特性,默认情况下处于禁用状态。要使用它,开发人员必须启用预览特性来编译代码才行,命令如下所示:

 

javac --release 21 --enable-preview Main.java
复制代码

 

运行程序也需要相同的标志:

 

java --enable-preview Main
复制代码

 

但是,可以使用源代码启动器直接运行该程序。在这种情况下,命令行如下:

 

java --source 21 --enable-preview Main.java
复制代码

 

jshell 选项也是可用的,但它也需要启用预览特性:

 

jshell --enable-preview
复制代码

 

Kotlin:下划线表示未使用的参数

在 Kotlin 中,下划线字符( _ )用于表示函数、lambda 或析构函数声明中未使用的参数。此特性允许开发人员省略此类参数的名称,从而生成更干净、更简洁的代码。

 

在 Kotlin 中,如果 lambda 的参数未使用,开发人员可以使用下划线来代替其名称。这在处理带有多个参数但只需其中某些参数的 lambda 函数时特别有用。

 

考虑如下的 Kotlin 代码片段:

 

mapOf(1 to "one", 2 to "two", 3 to "three")   .forEach { (_, value) -> println("$value!") }
复制代码

 

在本例中, forEach 函数需要一个 lambda,它接受两个参数:一个 key 和一个 value 。但是,我们只对 value 感兴趣,所以我们将 key 参数替换为下划线。

 

让我们考虑另一个代码片段:

 

var name: String by Delegates.observable("no name") {    kProperty, oldValue, newValue -> println("$oldValue")}
复制代码

 

在这种情况下,如果在 lambda 中不使用 kPropertynewValue 参数,那么将它们包括在内可能会徒劳且不清楚。使用下划线特性,开发人员可以简单地用下划线替换未使用的参数:

 

var name: String by Delegates.observable("no name") {    _, oldValue, _ -> println("$oldValue")}
复制代码

 

该特性在想要跳过某些组件的析构函数声明中也很有用:

 

val (w, x, y, z) = listOf(1, 2, 3, 4)print(x + z) // 'w'和'y'未被使用
复制代码

 

使用下划线特性,开发人员可以用下划线替换未使用的组件:

 

val (_, x, _, z) = listOf(1, 2, 3, 4)print(x + z)
复制代码

 

该特性并非 Kotlin 所独有。Haskell 等其他语言在模式匹配中使用下划线字符作为通配符。对于 C#来说,lambdas 中的“ _ ”只是一个习语,在语言中没有特殊处理。在 Java 的未来版本中可能也会应用相同的语义。

 

Scala:下划线的多功能性

在 Scala 中,下划线( _ )是一个用途广泛的通用字符。然而,这有时会导致混乱,并增加 Scala 新人的学习曲线。在本节中,我们将探讨 Scala 中下划线的不同用法和最常见的用法。

 

模式匹配和通配符

下划线被广泛用作通配符,用于匹配未知模式。这可能是 Scala 开发人员遇到的首个下划线用法。

 

模块导入

在导入包时,我们使用下划线来指示应该导入模块的所有或部分成员:

 

//导入包junit的所有成员。(相当于java中使用通配符*的导入)import org.junit._

// 导入junit中除Before之外的所有成员。import org.junit.{Before => _, _}

// 导入junit的所有成员,但将Before重命名为B4。import org.junit.{Before => B4, _}
复制代码

 

存在类型(Existential Type)

下划线还用作通配符,用于匹配类型创建器中的所有类型,如 List、Array、Seq、Option 或 Vector。

 

// 在List中使用下划线val list: List[_] = List(1, "two", true)println(list)

// 在Array中使用下划线val array: Array[_] = Array(1, "two", true)println(array.mkString("Array(", ", ", ")"))

// 在Seq中使用下划线val seq: Seq[_] = Seq(1, "two", true)println(seq)

// 在Option中使用下划线val opt: Option[_] = Some("Hello")println(opt)

// 在Vector中使用下划线val vector: Vector[_] = Vector(1, "two", true)println(vector)
复制代码

 

使用_ ,我们允许所有类型的元素进入内链表。

 

匹配

 

使用 match 关键字,开发人员可以使用下划线来捕获任何已定义的用例无法处理的所有可能情况。例如,给定一个商品的价格,购买或出售该商品的决定是基于某些特殊价格做出的。如果价格是 130,该商品就可以购买,但如果价格是 150,该商品就要出售。除上述价格外,其他价格都需要获得批准:

 

def itemTransaction(price: Double): String = { price match {   case 130 => "Buy"   case 150 => "Sell"

// 如果价格不是130和150中的任何一个,则执行这一情况 case _ => "Need approval" }}

println(itemTransaction(130)) // 购买println(itemTransaction(150)) // 出售println(itemTransaction(70)) // 需要批准println(itemTransaction(400)) // 需要批准
复制代码

 

忽略事物

下划线可以忽略代码中未使用的变量和类型。

 

忽略参数

 

例如,在函数执行中,开发人员可以使用下划线来隐藏未使用的参数:

 

val ints = (1 to 4).map(_ => "Hello")println(ints) // Vector(Hello, Hello, Hello, Hello)
复制代码

 

开发人员还可以使用下划线来访问嵌套集合:

 

val books = Seq(("Moby Dick", "Melville", 1851), ("The Great Gatsby", "Fitzgerald", 1925), ("1984", "Orwell", 1949), ("Brave New World", "Huxley", 1932))

val recentBooks = books .filter(_._3 > 1900) // 只筛选1900年以后出版的书 .filter(_._2.startsWith("F")) // 只筛选作者名字以“F”开头的书。// 只返回元组的第一个元素;书名

println(recentBooks) // List(The Great Gatsby)
复制代码

 

在本例中,下划线用于引用列表中元组的元素。 filter 函数只筛选满足给定条件的图书,然后 map 函数将元组转换为它们的第一个元素(书名)。结果是一个包含符合标准书名的序列。

 

忽略变量

 

当开发人员遇到不必要或不相关的细节时,他们可以使用下划线来忽略它们。

 

例如,开发人员只想要拆分字符串中的第一个元素:

 

val text = "a,b"val Array(a, _) = text.split(",")println(a)
复制代码

 

如果开发人员只想考虑构造中的第二个元素,那么同样的原则也适用。

 

val Array(_, b) = text.split(",")println(b)
复制代码

 

这一原则实际上可以扩展到两个以上的条目。例如,考虑下面的示例:

 

val text = "a,b,c,d,e"val Array(a, _*) = text.split(",")println(a)
复制代码

 

在本例中,开发人员将文本拆分成一个元素数组。然而,他们只对第一个元素 a 感兴趣。带星号的下划线( _* )会忽略数组中的其余条目,只关注所需的元素。

 

为了忽略第一个条目之后的其余条目,我们将下划线与 “* ”一起使用。

 

下划线也可以用于随机忽略:

 

val text = "a,b,c,d,e"val Array(a, b, _, d, e) = text.split(",")println(a)println(b)println(d)println(e)
复制代码

 

初始化为变量的默认值

 

当变量的初始值不是必需的时候,则可以使用下划线来作为默认值:

 

var x: String = _x = "real value"println(x) // 真实值
复制代码

 

然而,这并不适用于局部变量;局部变量必须要初始化。

 

转换

可以通过多种方式在转换中使用下划线。

 

函数重新分配(Eta 扩展)

 

使用下划线,可以将方法转换为函数。这对于将函数作为首选值来传递非常有用。

 

def greet(prefix: String, name: String): String = s"$prefix $name"

// Eta扩展,将greet变成一个函数val greeting = greet _

println(greeting("Hello", "John"))
复制代码

 

可变参数序列

 

序列可以使用 seqName:_* (类型归属的特殊实例)转换为可变参数。

 

def multiply(numbers: Int*): Int = { numbers.reduce(_ * _)}

val factors = Seq(2, 3, 4)val product = multiply(factors: _*)//使用factors: _*将Seq元素转换为变量

println(product) // 应该打印:24
复制代码

 

部分应用函数

 

通过在函数中只提供所需参数的一部分,并将其余部分留待稍后传递,开发人员可以创建所谓的部分应用函数(Partially-Applied Function)。用下划线替代尚未提供的参数。

 

def sum(x: Int, y: Int): Int = x + yval sumToTen = sum(10, _: Int)val sumFiveAndTen = sumToTen(5)

println(sumFiveAndTen, 15)
复制代码

 

在部分应用函数中使用下划线也可以被归类为忽略事物。开发人员可以忽略具有多个参数组的函数中的整组参数,从而创建一种特殊类型的部分应用函数:

 

def bar(x: Int, y: Int)(z: String, a: String)(b: Float, c: Float): Int = xval foo = bar(1, 2) _

println(foo("Some string", "Another string")(3 / 5, 6 / 5), 1)
复制代码

 

赋值运算符(Setter 重写)

 

可以认为重写默认的 setter 是一种使用下划线的转换:

 

class User { private var pass = "" def password = pass def password_=(str: String): Unit = {   require(str.nonEmpty, "Password cannot be empty")   require(str.length >= 6, "Password length must be at least 6 characters")   pass = str }}

val user = new Useruser.password = "Secr3tC0de"println(user.password) // 应该打印:"Secr3tC0de"

try { user.password = "123" //将会失败,应为它少于6个字符 println("Password should be at least 6 characters")} catch { case _: IllegalArgumentException => println("Invalid password")}
复制代码

 

高阶类型(Higher-Kinded Type)

高阶类型(Higher-Kinded Type)是对某种类型进行抽象,而这种类型又对另一种类型进行抽象。通过这种方式,Scala 可以跨类型构造函数进行泛化。它与存在类型(Existential Type)非常相似。可以使用下划线定义高阶类型:

 

trait Wrapper[F[_]] { def wrap[A](value: A): F[A]}

object OptionWrapper extends Wrapper[Option] { override def wrap[A](value: A): Option[A] = Option(value)}

val wrappedInt = OptionWrapper.wrap(5)println(wrappedInt)

val wrappedString = OptionWrapper.wrap("Hello")println(wrappedString)
复制代码

 

在上面的例子中, Wrapper 是一个具有高阶类型参数 F[_] 的 trait。它提供了一个方法 wrap ,用于将值包装到给定的类型中。 OptionWrapper 是为 Option 类型扩展此 trait 的对象。 F[_] 中的下划线表示任何类型,使 Wrapper 在所有 Option 类型中都是通用的。

 

这些例子都说明了 Scala 是一个强大的工具,它可以通过各种方式来简化和提高代码的可读性。这一特性非常符合 Scala 的理念,即成为一种简洁而富有表现力的语言,促进代码的可读性和可维护性。

 

结论

通过 JEP 443 在 Java 中引入未命名的模式和变量,标志着该语言发展的一个重要里程碑。该特性允许开发人员通过省略不必要的组件并替换未使用的变量来简化代码,使 Java 更接近 Kotlin 和 Scala 等语言的表达力和多功能性。

 

然而,需要注意的是,尽管这是向前迈出的重要一步,但 Java 在这一领域的历程仍然不完整。Kotlin 和 Scala 等语言长期以来一直采用类似的概念,并以各种方式使用它们来增强代码的可读性、可维护性和简洁性。这些语言已经证明了这些概念在使代码更高效、更容易理解方面的力量。

 

相比之下,Java 目前对未命名模式和变量的使用虽然有益,但仍有一定的局限性。Java 进一步利用这些概念的潜力巨大。该语言的未来更新可能会包含更多对未命名模式和变量的高级使用,从 Kotlin 和 Scala 等语言中汲取如何使用这些概念的灵感。

 

尽管如此,在 Java 中采用未命名的模式和变量是提高语言表达性和可读性的重要一步。随着 Java 的不断发展和壮大,我们希望看到这些概念的进一步创新使用,从而产生更高效和更可维护的代码。这段旅程还在继续,成为 Java 社区的一员是一个激动人心的时刻。

 

编码快乐!

 

原文链接:

https://www.infoq.com/articles/comparative-study-java-kotlin-scala/

2023-10-02 08:009261

评论

发布
暂无评论
发现更多内容

做开发5年,这8个高效开发好习惯我悟了🔥

引迈信息

程序员 前端 低代码 JNPF

软件测试丨Allure2报告中添加用例支持tags标签、失败重试功能

测试人

程序员 软件测试 测试开发 测试用例 Allure

想让ChatGPT和低代码开发实现完美结合?看这篇文章就行!

加入高科技仿生人

低代码 数字化 ChatGPT

华为云GaussDB,如何给世界一个更优选择?

YG科技

肝到爆!通过Canal如何优雅的将MySQL同步到ES?

Java全栈架构师

Java MySQL 程序员 后端 ES

看过才知道,这套SpringCloudAlibaba笔记,把微服务玩的出神入化!

程序知音

Java 微服务 SpringCloud java架构 后端技术

华为云GaussDB,如何为企业数字创新保驾护航?

YG科技

打卡有礼!快来 2023 开放原子全球开源峰会找龙蜥玩~

OpenAnolis小助手

开源 操作系统 龙蜥社区 开放原子全球开源峰会 龙蜥实验室

TDengine 合作伙伴 +1,这次是「DaoCloud道客」

爱倒腾的程序员

涛思数据 时序数据库 ​TDengine

PAG动效框架源码笔记 (五)渲染流程

olinone

ios android 动效 渲染

直播倒计时1天 | 一体化智能可观测平台如何保障电商节大促

博睿数据

电商 智能运维 博睿数据 直播预告

再也不怕“卡脖子”了?华为云数据库GaussDB究竟有什么神奇功能?

YG科技

优质高效!阿里内部超高质量的k8s+Jenkins笔记,技术与实战齐飞

程序知音

MegEngine 动态执行引擎-Imperative Runtime 概述

MegEngineBot

深度学习 开源 动态图 MegEngine

揭秘新一代云数仓技术架构与最佳实践

字节跳动数据平台

大数据 数据仓库 云原生 OLAP 数据仓库服务

提升效率:P4VFS让虚拟文件同步更迅速、更简单

龙智—DevSecOps解决方案

文件同步 虚拟文件同步 Virtual File Sync

国外云主机:为你的业务提供全球级托管!

一只扑棱蛾子

云主机

迈向新时代的英特尔代工服务:走差异化路径,坚持客户至上

最新动态

下载量破 15000!龙蜥社区登陆阿里云 ACR 制品中心 TOP5 榜单

OpenAnolis小助手

阿里云 操作系统 容器镜像 龙蜥社区 Dragonwell

聚焦 AIGC,函数计算为 AI 应用插上腾飞翅膀

Serverless Devs

Serverless FC AIGC

怎样区分试验与仿真的关系?

思茂信息

仿真软件 仿真技术

NFT全链游戏dapp系统开发合约定制

开发v-hkkf5566

软件测试/测试开发丨Allure2报告中添加附件-图片

测试人

程序员 软件测试 测试开发 Allure

【羊城晚报】WeOps智慧护航,传媒“领头羊”业务迈向新高度

嘉为蓝鲸

IT运维 传媒 传媒公司

如何保障医疗机器人的功能与安全?这几条编码标准你一定要了解

龙智—DevSecOps解决方案

医疗机器人 编码标准

2023上海国际嵌入式展 | 如何通过人工智能驱动的自动化测试工具提升嵌入式开发效率

龙智—DevSecOps解决方案

嵌入式 嵌入式软件 嵌入式设计 嵌入式开发

备战金九银十:大厂面试官必问MySQL连环炮全梳理,你扛得住嘛?

程序员小毕

Java MySQL 数据库 程序员 面试

嘉为蓝鲸研运一体化解决方案入选“鑫智奖”

嘉为蓝鲸

智能硬件 蓝鲸 金融数据

运维人员福音!自定义插件为运维提供更多可能

嘉为蓝鲸

#运维 Python运维 Linux 运维

几个小技巧,提高你的代码质量

飞算JavaAI开发助手

代码质量 程序员、 软件开发、

JVM语言比较研究:Java、Kotlin 和Scala的利与弊_编程语言_A N M Bazlur Rahman_InfoQ精选文章