写点什么

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:008934

评论

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

JDK17 Groovy Caffeine 模块化报错分享

FunTester

EMQ 和 Intel 评选工业物联网领域最佳案例与应用

新消费日报

质量保障工作的核心Roadmap

老张

软件测试 质量保障

评估SD-WAN的三个功能

Ogcloud

SD-WAN SD-WAN组网 SD-WAN服务商

【体验有奖】5 分钟函数计算部署 AI 艺术字应用,晒姓氏头像赢 Cherry 键盘!

阿里巴巴云原生

阿里云 Serverless 云原生 函数计算

软件测试学习笔记丨微信小程序自动化测试

测试人

小程序 软件测试 自动化测试 测试开发

Apple 发布 iMovie、Final Cut Pro、Compressor、Motion 的更新

南屿

基于volcano实现节点真实负载感知调度

快乐非自愿限量之名

架构 Volcano 负载测试

【Swift专题】聊聊Swift中的属性

珲少

业界声音|PolarDB最值得关注的技术创新有哪些?

阿里云瑶池数据库

数据库 云计算 阿里云 云原生 polarDB

SD-WAN技术:网络升级的智慧选择

Ogcloud

SD-WAN SD-WAN组网 SD-WAN服务商

EMQ 和 Intel 评选工业物联网领域最佳案例与应用

新消费日报

掌握 Robot Wramework:高效进行接口自动化

Liam

Jmeter 自动化测试 接口测试 测试工具 Robot Wramework

Mac软件精选壁纸软件:Backgrounds for Mac(桌面动态壁纸)

南屿

服装企业的配补调系统:从传统到智能的转型

第七在线

语音数据集在智能驾驶中的关键作用与应用

来自四九城儿

【技术探讨】一种多节点5Km(1.2M bps速率)实时Sub-G无线通信的物联网通讯解决方案

Geek_ab1536

免费好用的电子阅读神器MarginNote 3 for Mac

南屿

c4d r21中文破解版下载 C4D三维动画设计制作软件

南屿

多平台Java安装程序构建器 install4j for Mac v10.0.7中文激活版

南屿

京东广告算法架构体系建设--高性能计算方案最佳实践 | 京东零售广告技术团队

京东科技开发者

雷霆游戏加入鸿蒙“朋友圈”,《问道》手游启动鸿蒙原生应用开发

最新动态

【京东云新品发布月刊】2024年1月产品动态

京东科技开发者

选300平米别墅还是90平米小平层?一文带你读懂PolarDB分布式版集分一体化

阿里云瑶池数据库

数据库 云计算 阿里云 polarDB

Parallels Desktop 虚拟机提示“由于临界误差,不能启动虚拟机”怎么办

南屿

语音数据集:智能驾驶中车内语音识别技术的基石

来自四九城儿

鱼和熊掌如何兼得?一文解析RDS数据库存储架构升级

阿里云瑶池数据库

数据库 云计算 阿里云 云原生 polarDB

WMS系统与电商平台快速拉通库存数量

RestCloud

自动化 零代码 wms APPlink

mac电脑数据库管理工具:DBeaverEE v23.3.1企业激活版

iMac小白

Paste for Mac破解版(剪切板管理神器) 绿色安全无广告

南屿

分库分表已成为过去式,使用分布式数据库才是未来

不在线第一只蜗牛

数据库 源码 分布式 TiDB

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