Scala 在相当多的地方选择使用 _
符号一笔带过。在不同的地方,_
表达的意思并不完全一致。笔者在本期专门整理一篇有关于 Scala 的 _
用法。资料来源于:stackoverflow 和 简书论坛 。
1. 忽略类型参数
_
可用于忽略类型参数,和 Java 的 <?>
类似,比如:
//1. _ 表示忽略泛型参数,相当于 Java 中的<?>
def lengthOf(list: List[_]): Int = list.length
在这种情况下,该函数的具体功能和参数类型是什么无关。
2. 高级类型(higher-kind types)
我们先从 Java 谈起。 Java 支持这样的类型参数:
public class Higher<T> {}
但如果我们已知 T
又是包含某类型参数 ( 在这里假定它是 U
) 的泛型:
// 这样的写法是不通过的。
public class Higher<T<U>> {}
然而,在 Scala 中可以用 _
符号替代掉 T
包含的类型参数,下面举个例子来说明。
case class Fruit()
case class Candy()
case class Handbag[_]()
// C, 表示 Container, 是一个低阶泛型。是一个任何可能的容器。
// C[_] 是一个完整的泛型表达。
// 并没有规定这个容器内部装载何种类型的元素 ( 即程序的处理和这个内部泛型无关 ) ,因此这里使用 _ 作为占位符忽略掉。
// 相当于 Java 的 <?> 占位符。
case class Trunk[C[_]](){
var container : C[_] = _
}
object Test {
def main(args: Array[String]): Unit = {
val candyTrunk : Trunk[Handbag] = Trunk[Handbag]()
// Trunk 的容器 Handbag 可以装 Candy 或者是 Fruit。
candyTrunk.container = Handbag[Candy]()
candyTrunk.container = Handbag[Fruit]()
candyTrunk.container = Handbag()
}
}
由此可见,Scala 的泛型机制要比 Java 更加灵活。
3. 用于模式匹配
在模式匹配中, _
相当于 通配符号 (wildcard) ,用于忽略掉不需要的元素。比如:
p match {
case Some(_) => println("have a result.")
case _ => println("none.")
}
除此之外,在匹配集合时,还可以使用 @_*
符号通配后续的所有元素,并将它们再绑定到一个 Seq[T]
类型集合中:
val ints = List(1, 2, 3, 4)
ints match {
//将 3 和 4 绑定到一个 seq 中。
case List(1, 2, seq @_*) => println(seq.mkString(","))
case _ => println("none.")
}
4. 特质中的自身引用 (self-type)
若希望特质仅被某个满足条件的类所使用,就可以自身引用做限定,写法举例:
trait Tool {
//它表示这是一个专门用在 Computer 类的特质。
_ : Computer =>
//....
override def activate(): Unit = {
println("use tools.")
}
}
class Computer {
def activate() : Unit = println("Start")
}
其中,_
指代了特质本身,表明此特质仅可用于拓展 Computer
类。
5. 用于导入的情况
用于导入时,_
可以有两种含义。第一种用于通配,表示导入全部内容,比如导入 java.util
包的所有组件:
import java.util._
另外一种情况, _
也可以用于 "剔除" (或者称屏蔽)掉某个元素,比如:
import java.util.{Date => _,_}
它表示导入除了 Date
类以外的所有组件。注意这两个 _
的含义并不一致。
6. 初始化值
_
用于一个类的初始化赋值。注意,如果成员声明在构造器参数列表时,则不能使用 _
赋值。
// 成员 a 必须显式赋值
// 成员 b 可以通过 _ 赋默认值。
class Counter(var a : Int = 0 ){
var b : Int = _
}
7. 传名调用
我们希望将 def
定义的函数作为参数传入到另一个高阶函数中,则需要在函数后面加 _
表示传入的是函数本身。
def supply: Int = 2
def runBlock(block: () => Int): Unit = {
println(s"result =${block.apply()}")
}
//并非将 supply 的返回值传入进去,而是将 supply 作为一个 ()=>Int 传入到参数中。
runBlock(supply _)
然而一般情况下,我们会直接传入一个匿名函数。
8. 省略匿名表达式参数
该用法在 filter
, map
, flatMap
等方法中经常使用,可以将 _
理解为代词,表示 "这个匿名函数的参数"。注意,多个 _
按顺序指代不同的匿名表达式的参数。
val ints = List(1,2,3,4,5)
// 完整写法是 (p:Int) => p + 2
ints.map(_ + 2)
// 完整写法是 (p : Int, q: Int) => p + q
// 这两个 _ 分别代表匿名函数的第一个 p 和第二个参数 q。
ints.reduce(_ + _)
9. 将数组传递到不定参数中
Scala 不支持将数组直接传入到一个不定参参数中,此时使用 _*
符号进行转化。
val ints = List(1,2,3,4)
def sum(ints: Int*): Int = ints.sum
//_* 表示 ints 内的每一个元素。
sum(ints : _*)