Scala 之关于 _ 符号的妙用

703 阅读4分钟

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 = 2def 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 : _*)