Scala中的下划线(underscore)应用场景

下划线这个符号几乎贯穿了任何一本Scala编程书籍,并且在不同的场景下具有不同的含义,绕晕了不少初学者。正因如此,下划线这个特殊符号无形中增加Scala的入门难度。本文希望帮助初学者踏平这个小山坡。

  1. 用于替换Java的等价语法

由于大部分的Java关键字在Scala中拥有了新的含义,所以一些基本的语法在Scala中稍有变化。

1.1 导入通配符

*在Scala中是合法的方法名,所以导入包时要使用_代替。

//Java
import java.util.*;

//Scala
import java.util._
1.2 类成员默认值

Java中类成员可以不赋初始值,编译器会自动帮你设置一个合适的初始值:

class Foo{
//String类型的默认值为null
String s;
}
而在Scala中必须要显式指定,如果你比较懒,可以用_让编译器自动帮你设置初始值:

class Foo{
//String类型的默认值为null
var s: String = _
}
该语法只适用于类成员,而不适用于局部变量。

1.3 可变参数

Java声明可变参数如下:

public static void printArgs(String ... args){
for(Object elem: args){
System.out.println(elem + " ");
}
}
调用方法如下:

//传入两个参数
printArgs("a", "b");
//也可以传入一个数组
printArgs(new String[]{"a", "b"});
在Java中可以直接将数组传给printArgs方法,但是在Scala中,你必须要明确的告诉编译器,你是想将集合作为一个独立的参数传进去,还是想将集合的元素传进去。如果是后者则要借助下划线:

printArgs(List("a", "b"): _*)
1.4 类型通配符

Java的泛型系统有一个通配符类型,例如List<?>,任意的List<T>类型都是List<?>的子类型,如果我们想编写一个可以打印所有List类型元素的方法,可以如下声明:

public static void printList(List<?> list){
for(Object elem: list){
System.out.println(elem + " ");
}
}
对应的Scala版本为:

def printList(list: List[_]): Unit ={
list.foreach(elem => println(elem + " "))
}
2 模式匹配

2.1 默认匹配

str match{
case "1" => println("match 1")
case _ => println("match default")
}
2.2 匹配集合元素

//匹配以0开头,长度为三的列表
expr match {
case List(0, _, _) => println("found it")
case _ =>
}

//匹配以0开头,长度任意的列表
expr match {
case List(0, _*) => println("found it")
case _ =>
}

//匹配元组元素
expr match {
case (0, _) => println("found it")
case _ =>
}

//将首元素赋值给head变量
val List(head, _*) = List("a")

  1. Scala特有语法

3.1 访问Tuple元素

val t = (1, 2, 3)
println(t._1, t._2, t._3)
3.2 简写函数字面量(function literal)

如果函数的参数在函数体内只出现一次,则可以使用下划线代替:

val f1 = (: Int) + (: Int)
//等价于
val f2 = (x: Int, y: Int) => x + y

list.foreach(println(_))
//等价于
list.foreach(e => println(e))

list.filter(_ > 0)
//等价于
list.filter(x => x > 0)
3.3 定义一元操作符

在Scala中,操作符其实就是方法,例如1 + 1等价于1.+(1),利用下划线我们可以定义自己的左置操作符,例如Scala中的负数就是用左置操作符实现的:

-2
//等价于
2.unary_-
3.4 定义赋值操作符

我们通过下划线实现赋值操作符,从而可以精确地控制赋值过程:

class Foo {
def name = { "foo" }
def name_=(str: String) {
println("set name " + str)
}

val m = new Foo()
m.name = "Foo" //等价于: m.name_=("Foo")

3.5 定义部分应用函数(partially applied function)

我们可以为某个函数只提供部分参数进行调用,返回的结果是一个新的函数,即部分应用函数。因为只提供了部分参数,所以部分应用函数也因此而得名。

def sum(a: Int, b: Int, c: Int) = a + b + c
val b = sum(1, _: Int, 3)
b: Int => Int = <function1>
b(2) //6
3.6 将方法转换成函数

Scala中方法和函数是两个不同的概念,方法无法作为参数进行传递,也无法赋值给变量,但是函数是可以的。在Scala中,利用下划线可以将方法转换成函数:

//将println方法转换成函数,并赋值给p
val p = println _
//p: (Any) => Unit

  1. 小结

第一:初始化的时候。
object Sample {
var name:String=_
def main (args: Array[String]){
name="hello world"
println(name)
}

在这里,name也可以声明为null,例:var name:String=null。这里的下划线和null的作用是一样的。
第二:引入的时候。
import math._
object Sample {
def main (args: Array[String]){
println(BigInt(123))
}
}
这里的math.就相当于Java中的math.*; 即“引用包中的所有内容”。
第三:集合中使用。(最典型,最常用)
object Sample {
def main (args: Array[String]){
val newArry= (1 to 10).map(
2)
println(newArry)
}
}
这里的下划线代表了集合中的“某(this)”一个元素。这个用法很常见,在foreach等语句中也可以使用。
第四:模式匹配。
object Sample {
def main (args: Array[String]){
val value="a"
val result= value match{
case "a" => 1
case "b" => 2
case _ =>"result"
}
println(result)
}
}
在这里的下划线相当于“others”的意思,就像Java switch语句中的“default”。
还有一种写法,是被Some“包”起来的,说明Some里面是有值的,而不是None。
object Sample {
def main (args: Array[String]){
val value=Some("a")
val result= value match{
case Some() => 1
case _ =>"result"
}
println(result)
}
还有一种表示队列
object Sample {
def main (args: Array[String]){
val value=1 to 5
val result= value match{
case Seq(
,_
) => 1
case _ =>"result"
}
println(result)
}
}
第五:函数中使用。
object Sample {
def main (args: Array[String]){
val set=setFunction(3.0,:Double)
println(set(7.1))
}
def setFunction(parm1:Double,parm2:Double): Double = parm1+parm2
}
这是Scala特有的“偏函数”用法。
第六:元组Tuple。(如果这也算是的话)
object Sample {
def main (args: Array[String])={
val value=(1,2)
print(value.1)
}
}
第七:传参。
object Sample {
def main (args: Array[String])={
val result=sum(1 to 5:
)
println(result)
}
def sum(parms:Int
)={
var result=0
for(parm <- parms)result+=parm
result
}
}
当函数接收的参数不定长的时候,假如你想输入一个队列,可以在一个队列后加入“:
*”,因此,这里的“1 to 5”也可以改写为:“Seq(1,2,3,4,5)”。这算是一个小的用法吧

1、作为“通配符”,类似Java中的*。如
import Scala.math._

2、:*作为一个整体,告诉编译器你希望将某个参数当作参数序列处理!例如val s = sum(1 to 5:*)就是将1 to 5当作参数序列处理。

3、指代一个集合中的每个元素。例如我们要在一个Array a中筛出偶数,并乘以2,可以用以下办法:a.filter(%2==0).map(2*)。又如要对缓冲数组ArrayBuffer b排序,可以这样:val bSorted = b.sorted(_

4、在元组中,可以用方法_1, _2, _3访问组员。如a._2。其中句点可以用空格替代。

5、使用模式匹配可以用来获取元组的组员,例如val (first, second, third) = t但如果不是所有的部件都需要,那么可以在不需要的部件位置上使用_。比如上一例中val (first, second, _) = t

6、还有一点,下划线_代表的是某一类型的默认值。对于Int来说,它是0。对于Double来说,它是0.0对于引用类型,它是null。

1、作为“通配符”
类似Java中的*。如:
import scala.math._

2、:*作为一个整体
告诉编译器你希望将某个参数当作参数序列处理!例如val s = sum(1 to 5:
*)就是将1 to 5当作参数序列处理。

3、占位符 ,指代一个集合中的每个元素。
例如我们要在一个Array a中筛出偶数,并乘以2,可以用以下办法:
a.filter(%2==0).map(2*)
List(1,2,3).foreach(println(_))

对缓冲数组ArrayBuffer b排序,可以这样:
val bSorted = b.sorted(_)

4、在元组中,可以用方法_1, _2, _3访问组员
_ 则为eta-conversion (lambda表达式支持的一种变换 )的入参缩写形式,Scala里 a => foo(a) 经过eta-conversion后,直接就是右边的函数名foo,缩写为foo(*),这里t => t._2 (lambda表达式 ) 可直接写成 *._2

map(_._2) 等价于 map(t => t._2) //t是个2项以上的元组

map(_._2, _) 等价与 map(t => t._2, t) //这会返回第二项为首后面项为旧元组的新元组

._n 为获取元组第n项

5、使用模式匹配可以用来获取元组的组员
如:
val (first, second, third) = t

但如果不是所有的部件都需要,那么可以在不需要的部件位置上使用_。比如上一例中val (first, second, _) = t

6、还有一点,下划线_代表的是某一类型的默认值。

对于Int来说,它是0。
对于Double来说,它是0.0
对于引用类型,它是null。

7.类的setter方法,比如类A中定义了var f,则相当于定义了setter方法 f_=。也可以自己定义f_= 方法来完成更多的事情,比如设置前作一些判断或预处理之类的操作

1、存在性类型:Existential types
def foo(l: List[Option[_]]) = ...

2、高阶类型参数:Higher kinded type parameters
case class A[K[_],T](a: K[T])

3、临时变量:Ignored variables
val _ = 5

4、临时参数:Ignored parameters
List(1, 2, 3) foreach { _ => println("Hi") }

5、通配模式:Wildcard patterns
Some(5) match { case Some() => println("Yes") }
match {
case List(1,
,) => " a list with three element and the first element is 1"
case List(
*) => " a list with zero or more elements "
case Map[,] => " matches a map with any key type and any value type "
case _ =>
}
val (a, ) = (1, 2)
for (
<- 1 to 10)

6、通配导入:Wildcard imports
import java.util._

7、隐藏导入:Hiding imports
// Imports all the members of the object Fun but renames Foo to Bar
import com.test.Fun.{ Foo => Bar , _ }

// Imports all the members except Foo. To exclude a member rename it to _
import com.test.Fun.{ Foo => _ , _ }

8、连接字母和标点符号:Joining letters to punctuation
def bang_!(x: Int) = 5

9、占位符语法:Placeholder syntax
List(1, 2, 3) map (_ + 2)
_ + _
( (: Int) + (: Int) )(2,3)

val nums = List(1,2,3,4,5,6,7,8,9,10)

nums map (_ + 2)
nums sortWith(>)
nums filter (_ % 2 == 0)
nums reduceLeft(+)
nums reduce (_ + )
nums reduceLeft(
max )
nums.exists(
> 5)
nums.takeWhile(_ < 8)

10、偏应用函数:Partially applied functions
def fun = {
// Some code
}
val funLike = fun _

List(1, 2, 3) foreach println _

1 to 5 map (10 * _)

//List("foo", "bar", "baz").map(_.toUpperCase())
List("foo", "bar", "baz").map(n => n.toUpperCase())

11、初始化默认值:default value
var i: Int = _

12、作为参数名:
//访问map
var m3 = Map((1,100), (2,200))
for(e<-m3) println(e._1 + ": " + e._2)
m3 filter (e=>e.1>1)
m3 filterKeys (
>1)
m3.map(e=>(e._1*10, e._2))
m3 map (e=>e._2)

//访问元组:tuple getters
(1,2)._2

13、参数序列:parameters Sequence
*作为一个整体,告诉编译器你希望将某个参数当作参数序列处理。例如val s = sum(1 to 5:)就是将1 to 5当作参数序列处理。
//Range转换为List
List(1 to 5:_
)

//Range转换为Vector
Vector(1 to 5: _*)

//可变参数中
def capitalizeAll(args: String*) = {
args.map { arg =>
arg.capitalize
}
}

val arr = Array("what's", "up", "doc?")
capitalizeAll(arr: _*)

以上是我所能想到的,欢迎留言补充!
下划线在大部分的应用场景中是以语法糖的形式出现的,可以减少击键次数,并且代码显得更加简洁。但是对于不熟悉下划线的同学阅读起来稍显困难,希望通过本文能够帮你解决这个的困惑。本文成文仓促,如有遗漏,欢迎留言! 转载请注明作者: joymufeng

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容