【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

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

评论

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

玩转服务器之Java Web篇:手把手教你搭建Java Web环境 | 京东云技术团队

京东科技开发者

Java 云服务器 京东云 企业号 5 月 PK 榜

几种常见的Python数据结构

华为云开发者联盟

Python 开发 华为云 华为云开发者联盟 企业号 5 月 PK 榜

什么是人工智能领域的过拟合和欠拟合

Jerry Wang

人工智能 机器学习 深度学习 强化学习 三周年连更

当Serverless遇到Regionless:现状与挑战

华为云开发者联盟

云原生 后端 华为云 华为云开发者联盟 企业号 5 月 PK 榜

极光笔记 | 极光推出“运营增长”解决方案,开启企业增长新引擎

极光JIGUANG

营销 运营 用户运营

和写作谈谈感觉,你也许可以这样做。

叶小鍵

Android App开发超实用实例 | ​Broadcast

TiAmo

broadcast broadcastreceiver Android APP

守护企业网站安全!选择华为云网站安全方案更准

YG科技

京东APP百亿级商品与车关系数据检索实践 | 京东云技术团队

京东科技开发者

数据库 京东云 企业号 5 月 PK 榜

惊艳的数据可视化案例 让可视化设计灵感迸发

2D3D前端可视化开发

数据分析 数据可视化 数据可视化工具 前端数据可视化 数据可视化设计

kafka高性能设计之内存池

Java你猿哥

Java kafka ssm 架构师 内存池

GitHub最新爆火,2023年最新版互联网大厂Java八股文合集PDF版出炉

开心学Java

Java 面试 Java八股文

华为云网站安全解决方案:让企业上云后无忧开展网站业务

YG科技

华为云网站安全解决方案助力企业腾“云”驾“务”

YG科技

All in AI,现在开始算不算太晚?

Baihai IDP

人工智能 AI 企业号 5 月 PK 榜 人工智能浪潮

Spring Boot:MyBatis分页

Java你猿哥

Java spring Spring Boot mybatis ssm

试用「ChatGPT」几周之后

人工智能 ChatGPT

耗时15天,我把“大厂面试指南”进行了重新梳理,V2.0版已上线

Java你猿哥

Java 数据库 计算机 java面试 java基础

华为云网站安全解决方案一站式护航

YG科技

RabbitMQ - 1消息队列中间件AMQP协议、和主要角色

Java你猿哥

Java ssm AMQP Rabbit MQ

外译笔记 | 比尔盖茨:AI与智能手机和互联网一样具有革命性

京东科技开发者

AI 京东云 企业号 5 月 PK 榜

为什么老有人想让我们“程序员”失业? | 社区征文

坚果

三周年征文

Spring Data JPA:轻松实现数据持久化

Java你猿哥

Java spring ssm spring data

全网好评!程序员面试必备的Java八股文,适合所有的Java求职者!

Java你猿哥

Java Spring Boot 多线程 java基础 Java八股文

浪潮信息 KOS 助力企业核心业务完成 CentOS 迁移替换,性能提升 10%|龙蜥案例

OpenAnolis小助手

操作系统 开源社区 CentOS迁移 浪潮信息 龙蜥案例

mosn基于延迟负载均衡算法——走得更快,期待走得更稳 | 京东云技术团队

京东科技开发者

负载均衡 京东云 企业号 5 月 PK 榜

优雅!用了这两款插件,我成了整个公司代码写得最规范的码农

AI乔治

Java 架构 面试 IDEA java代码规范

京东小程序折叠屏适配探索 | 京东云技术团队

京东科技开发者

京东云 企业号 5 月 PK 榜 京东小程序

另一个CI/CD构建工具

weichenqi

DevOps 云原生 运维平台

多位P8大佬联合打造的Java面试八股文,堪称《Java驾考宝典》

Java你猿哥

Java MySQL redis spring 多线程

获得清晰、无噪点的图像:Topaz Photo AI 激活电脑版

真大的脸盆

Mac Mac 软件 图像降噪 图像噪点处理

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