1. 概述
下划线 _
是 Scala 中广泛使用的一个符号,有时它被称为“语法糖”,因为它可以让代码更简洁、更短。然而,这也常常带来困惑,增加了学习曲线。
在本篇文章中,我们将介绍 Scala 中下划线 _
最常见的一些用法。
2. 版本警告 ⚠️
在 Scala 3 中,一些下划线的用法已被弃用或移除,例如 通配符参数 和 变长参数展开 的语法,新的语法已经替代了这些用法。本文中讨论的特性在 Scala 2 中可以正常使用,但在 Scala 3 中可能并不完全兼容。
3. 模式匹配与通配符
下划线最常见的用途之一是作为通配符,用于匹配未知的模式。这也是我们学习 Scala 时最先接触到的用法之一。
3.1. 模块导入
在导入包时,我们可以使用下划线来表示导入模块中的所有成员(相当于 Java 中的 *
导入):
// 导入 junit 包中的所有成员(等价于 Java 中的 import org.junit.*)
import org.junit._
// 导入 junit 中除 Before 外的所有成员
import org.junit.{Before => _, _}
// 导入 junit 中所有成员,并将 Before 重命名为 B4
import org.junit.{Before => B4, _}
3.2. 存在类型
在类型构造器中(如 List
、Array
、Seq
、Option
、Vector
等),我们可以使用下划线作为通配符来匹配所有类型:
def getLength(x : List[List[_]]): Int = x.length
assertEquals(getLength(List(List(8), List("str"))), 2)
assertEquals(getLength(List(List(5.00), List("str"))), 2)
assertEquals(getLength(List(List(Array(7)), List("str"))), 2)
通过使用 _
,我们允许内层 List
中存储任意类型的元素。
3.3. 模式匹配
使用 match
关键字时,我们可以使用下划线来捕获所有未被显式处理的模式:
def itemTransaction(price: Double): String = {
price match {
case 130 => "Buy"
case 150 => "Sell"
// 如果价格不是 130 或 150,则执行此分支
case _ => "Need approval"
}
}
itemTransaction(130) // Buy
itemTransaction(150) // Sell
itemTransaction(70) // Need approval
itemTransaction(400) // Need approval
我们也可以参考我们的 模式匹配 教程了解更多示例。
4. 忽略内容
下划线可以用来忽略代码中不需要使用的变量或类型。
4.1. 忽略参数
在函数执行中,我们可以使用下划线来忽略未使用的参数:
val ints = (1 to 4).map(_ => "Int")
assertEquals(ints, Vector("Int", "Int", "Int", "Int"))
在这个例子中,我们忽略了 map
函数中的参数,直接返回 "Int"
。
也可以使用下划线作为占位符来简化函数调用:
val prices = Seq(10.00, 23.38, 49.82)
val pricesToInts = prices.map(_.toInt)
assertEquals(pricesToInts, Seq(10, 23, 49))
这等价于:
prices.map(x => x.toInt)
还可以使用下划线访问嵌套集合:
val items = Seq(("candy", 2, true), ("cola", 7, false), ("apple", 3, false), ("milk", 4, true))
val itemsToBuy = items
.filter(_._3) // 只保留可用商品(true)
.filter(_._2 > 3) // 只保留价格大于 3 的商品
.map(_._1) // 只保留商品名
assertEquals(itemsToBuy, Seq("milk"))
4.2. 忽略变量
在解构赋值中,如果只关心部分变量,可以用下划线忽略其他变量:
val text = "a,b"
val Array(a, _) = text.split(",")
assertEquals(a, "a")
如果只关心第二个元素:
val Array(_, b) = text.split(",")
assertEquals(b, "b")
也可以忽略多个元素:
val text = "a,b,c,d,e"
val Array(a, _*) = text.split(",")
assertEquals(a, "a")
如果只想忽略某个特定元素:
val Array(a, b, _, d, e) = text.split(",")
assertEquals(a, "a")
assertEquals(b, "b")
assertEquals(d, "d")
assertEquals(e, "e")
4.3. 变量初始化为默认值
当变量不需要初始值时,可以使用下划线将其初始化为默认值:
var x: String = _
x = "real value"
println(x) // real value
⚠️ 注意:这种方式不适用于局部变量,局部变量必须显式初始化。
5. 类型转换与函数转换
下划线在类型转换和函数转换中也有多种用途。
5.1. 方法转函数(Eta 展开)
使用下划线可以把一个方法转换为函数:
def multiplier(a: Int, b: Int): Int = a * b
val times = multiplier _ // 将 multiplier 方法转换为函数
assertEquals(multiplier(8, 13), times(8, 13))
5.2. 序列转可变参数
可以使用 seqName: _*
将序列转换为可变参数:
def sum(args: Int*): Int = {
args.reduce(_ + _)
}
val sumable = Seq(4, 5, 10, 3)
val sumOfSumable = sum(sumable: _*) // 将序列转换为可变参数
assertEquals(sumOfSumable, 22)
5.3. 部分应用函数
通过只提供部分参数,可以生成一个部分应用函数,未提供的参数用下划线代替:
def sum(x:Int,y:Int): Int = x + y
val sumToTen = sum(10, _: Int)
val sumFiveAndTen = sumToTen(5)
assertEquals(sumFiveAndTen, 15)
⚠️ 这种使用方式也可以归类为“忽略内容”。
还可以忽略参数组来生成部分应用函数:
def bar(x:Int,y:Int)(z:String,a:String)(b:Float,c:Float): Int = x
val foo = bar(1,2) _
assertEquals(foo("Some string", "Another string")(3/5, 6/5), 1)
5.4. 赋值操作符(重写 setter)
使用下划线可以重写默认的 setter 方法:
class Product {
private var a = 0
def price = a
def price_=(i: Int): Unit = {
require(i > 10)
a = i
}
}
val product = new Product
product.price = 20
assertEquals(product.price, 20)
try {
product.price = 7 // 会失败,因为 7 不大于 10
fail("Price must be greater than 10")
} catch {
case _: IllegalArgumentException => assertNotEquals(product.price, 7)
}
6. 其他用途
还有一些不属于上述分类的用法。
6.1. 连接字母与操作符/标点符号
在变量名中不能直接使用标点符号,但可以通过下划线连接字母与标点符号:
def list_++(list: List[_]): List[_] = List.concat(list, list)
val concatenatedList = list_++(List(2, 5))
assertEquals(concatenatedList, List(2, 5, 2, 5))
6.2. 数字字面量分隔符(Scala 2.13+)
从 Scala 2.13 开始,可以使用下划线作为数字字面量的分隔符:
var x = 1_000_000 // 1000000
x = 1_00_00_00 // 1000000
x = 1_0000_00 // 1000000
var pi = 3_14e-0_2 // 3.14
pi = 3_14e-02 // 3.14
pi = 314e-0_2 // 3.14
pi = 314e-02 // 3.14
6.3. 高阶类型(Higher-Kinded Type)
高阶类型是对类型构造器的抽象,类似于存在类型。可以使用下划线定义高阶类型:
trait ObjectContainer[T[_]] { // 高阶类型参数
def checkIfEmpty(collection: T[_]): Boolean
}
object SeqContainer extends ObjectContainer[Seq] {
override def checkIfEmpty(collection: Seq[_]): Boolean = !collection.nonEmpty
}
var seqIsEmpty = SeqContainer.checkIfEmpty(Seq(7, "7"))
assertTrue(seqIsEmpty == false)
seqIsEmpty = SeqContainer.checkIfEmpty(Seq())
assertTrue(seqIsEmpty == true)
7. 总结
在本文中,我们介绍了 Scala 中下划线 _
的多种常见用法。这些用法涵盖了从模式匹配、忽略变量到函数转换等多个方面。
✅ 项目源码可以在 GitHub 上找到。