Scala 特殊符号操作符汇总

摘要:本文汇总在 scala 中遇到各种符号操作符。

模式匹配中使用 @

这个 @ 的主要功能是在模式匹配中,匹配到一个模式后,但是在处理过程中,使用对象本身而不是匹配后的元素。

案例1:匹配 Some()

参考该 Overflow 回答

当我们匹配对象是否为 Some(x) 的时候,如果没有 @ 那么最后我们在匹配中的值将是 Some(x) 中的 x, 而如果加入 @,那么最后匹配的将会是 Some(x)

It enables one to bind a matched pattern to a variable. Consider the following, for instance:

1
val o: Option[Int] = Some(2)

You can easily extract the content:

1
2
3
4
o match {
case Some(x) => println(x)
case None =>
}

But what if you wanted not the content of Some, but the option itself? That would be accomplished with this:

1
2
3
4
o match {
case x @ Some(_) => println(x)
case None =>
}

Note that @ can be used at any level, not just at the top level of the matching.

案例2:API 匹配 Request

1
2
3
4
5
6
7
8
9
def intent = {
case req @ GET(Path(Seg("api" :: "user" :: IntPathElement(userId) :: Nil))) =>
val f = (userManager ? FindUserById(userId))
respond(f, req)

case req @ GET(Path(Seg("api" :: "user" :: Nil))) & Params(EmailParam(email)) =>
val f = (userManager ? FindUserByEmail(email))
respond(f, req)
}

在处理 request 请求的时候,需要匹配请求的路径,然后还需要直接使用 requet

下划线 _ 的使用场景

import 通配符

1
import org.apache.spark.SparkContext._

集合操作指代每一个元素

在所有的集合操作中都可以使用下划线指代集合内容。

1
2
3
4
5
6
object Sample {
def main (args: Array[String]){
val newArry= (1 to 10).map(_*2)
println(newArry)
}
}

在一个 Array a中筛出偶数,并乘以2

1
a.filter(_%2==0).map(2*_)

模式匹配使用

主要用于匹配通配的情形

1
2
3
4
5
6
7
8
9
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)

其他案例

1
2
val m = Map(1 -> 2,2 -> 4)
for ((k,_) <- m) println(k) //如果不需要所有部件, 则在不需要的部件使用_; 本例只取key,因此在value处用_

变量初始化为 null

1
2
3
4
5
6
object Sample {
var name:String=_
def main (args: Array[String]){
name="hello world"
println(name)
}

在这里,name 也可以声明为 null,例:var name:String=null。这里的下划线和 null 的作用是一样的。

:_* 参数序列处理

:_* 作为一个整体,告诉编译器你希望将某个参数当作参数序列处理!

例如,当函数接收的参数不定长的时候,假如你想输入一个队列,可以在一个队列后加入“:_*”,因此,这里的“1 to 5”也可以改写为:“Seq(1,2,3,4,5)”。

1
2
3
4
5
6
7
8
9
10
11
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, _2, _3访问组员。如 a._2。其中点操作符可以用空格替代,也就是 a _2

1
2
3
4
5
6
object Sample {
def main (args: Array[String])={
val value=(1,2)
print(value._1)
}
}

匿名函数 =>

这个比较简单,因为 Java8Lambda 已经广泛使用。

=> 匿名函数(Anonymous Functions): 表示创建一个函数实例。

比如:(x: Int) => x + 1

和如下JAVA方法表示的含义一样:

1
2
3
public int function(int x){
return x+1;
}

可以这么理解:

  1. =>左边 是输入参数,: 后面 int 是参数类型
  2. =>右边 当作函数体, 类似匿名函数的 {}

模式匹配中配合 case

1
2
3
4
5
6
7
8
val bools = Seq(true, false)

for (bool <- bools) {
bool match {
case true => println("Got heads")
case false => println("Got tails")
}
}

集合遍历 <-

简单说这是一个集合遍历的方法:

1
2
3
4
var list = Array(1,2,3,4)
for (aa <- list) {
printf(aa+" ")
}

但是在 for 推导式中,这个符号表示生成器,其解释比循环更加复杂,是一个 scala 的语法糖,最后会被解释为一系列的容器操作:mapflatMap 等。参考另一篇详细介绍 Scala For 推导式的文章

集合拼接 ++= —= 等操作

参考官方集合 Set 文档

++= 用于拼接容器,而 += 用于拼接元素。

WHAT IT IS WHAT IT DOES

加法:

  1. xs += x 把元素 x 添加到集合 xs 中。该操作有副作用,它会返回左操作符,这里是 xs 自身。
  2. xs += (x, y, z) 添加指定的元素到集合 xs 中,并返回 xs 本身。(同样有副作用)
  3. xs ++= ys 添加集合 ys 中的所有元素到集合 xs 中,并返回 xs 本身。(表达式有副作用)
  4. xs add x 把元素 x 添加到集合 xs 中,如集合 xs 之前没有包含 x,该操作返回 true,否则返回 false。

移除:

  1. xs -= x 从集合 xs 中删除元素 x,并返回 xs 本身。(表达式有副作用)
  2. xs -= (x, y, z) 从集合 xs 中删除指定的元素,并返回 xs 本身。(表达式有副作用)
  3. xs —= ys 从集合 xs 中删除所有属于集合 ys 的元素,并返回 xs 本身。(表达式有副作用)
  4. xs remove x 从集合 xs 中删除元素 x 。如之前 xs 中包含了 x 元素,返回 true,否则返回 false。
  5. xs retain p 只保留集合 xs 中满足条件 p 的元素。
  6. xs.clear() 删除集合 xs 中的所有元素。

更新:

  1. xs(x) = b ( 同 xs.update(x, b) )参数 b 为布尔类型,如果值为 true 就把元素x加入集合 xs,否则从集合 xs 中删除 x。

克隆:

  1. xs.clone 产生一个与 xs 具有相同元素的可变集合。

冒号操作符

:::运算符

三个冒号表示List的连接操作,比如:

1
2
3
val a = List(1,2)
val b = List(3,4)
val c = a:::b //c=List(1,2,3,4)

::两个冒号

两个冒号表示普通元素与List的连接操作,比如:

1
2
3
val a=1
val b=List(66,88)
val c = 1::b //c=List(1,66,88)

元组操作 ->

scala中元组含义:

元组是不同类型的值聚集线程的列表
通过将多个值使用小括号括起来,即表示元组

scala中元组与数组区别

数组中元素 数据类型必须一样,但是元组数据类型可以不同。

1
2
3
4
5
6
val first = (1,2,3) // 定义三元元组
val one = 1
val two = 2
val three = one -> two
println(three) // 构造二元元组
println(three._2) // 访问二元元组中第二个值

上下界约束符 <: 与 >:

这对符号个人觉得是里面最好理解的了,这对符号用于写范型类/函数时约束范型类型。

1
2
3
4
5
6
def using[A <: Closeable, B](closeable: A) (getB: A => B): B =
try {
getB(closeable)
} finally {
closeable.close()
}

例子中 A <: Closeable(java.io.Cloaseable) 的意思就是保证类型参数 ACloseable 的子类(含本类)。
语法“A <: B“定义了B为A的上界;同理相反的 A >: B 的意思就是A是B的超类(含本类),定义了B为A的下界。
其实 <:>: 就等价于java范型编程中的 extendssuper

协变与逆变符号+T, -T

“协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型。e.g. String => AnyRef

“逆变”则是指能够使用派生程度更小的类型。e.g. AnyRef => String

【+T】表示协变,【-T】表示逆变

参考 《Scala 程序设计第2版》 2.13 抽象类型和参数化类型:

Scala 支持参数化类型,与 Java 中的泛型十分类似。这两个术语,但 Scala 社区中多使用“参数化类型”, Java 社区中常用泛型一词。)在语法上, Java 使用尖括号( <… >),而 Scala 使用方括号( [… ]),因为在 Scala 中 <> 常用作方法名。

例如,字符串列表可以声明如下:

1
val strings: List[String] = List("one", "two", "three")

由于我们可以在集合 List[A] 中使用任何类型作为类型 A,这种特性被称为参数多态。在方法 List 的通用实现中,允许使用任何类型的实例作为 List 的元素。

A 之前的 + 表示:如果 BA 的子类,则 List[B] 也是 List[A] 的子类型,这被称为协类型。协类型很符合直觉,如果我们有一个函数 f(list: List[Any]),那么传递 List[String] 给这个函数,也应该能正常工作。

如果类型参数前有 -,则表示另一种关系:如果 BA 的子类型,且 Foo[A] 被声明为 Foo[-A],则 Foo[B]Foo[A] 的父类型(称为逆类型)。这一机制没那么符合直觉,我们将在参数化类型中与参数化类型的其他细节一起解释这一点。

Scala 还支持另一种被称为“抽象类型”的抽象机制,它可以运用在许多参数化类型中,也能够解决设计上的问题。然而,尽管两种机制有所重合,但并不冗余,两种机制对不同的设计问题各有优势与不足。

view bounds(视界) 与 <%

<%的意思是“view bounds”(视界),它比<:适用的范围更广,除了所有的子类型,还允许隐式转换过去的类型

1
def method [A <% B](arglist): R = ...

等价于

1
def method [A](arglist)(implicit viewAB: A => B): R = ...

表示 A 可以视为类型 B

案例2:

视界,就像类型边界,要求存在一个能够将某类型转换为指定类型的函数。你可以使用 <% 指定类型限制,例如:

1
2
scala> class Container[A <% Int] { def addIt(x: A) = 123 + x }
defined class Container

这是说 A 必须“可被视作” Int 。让我们试试。

1
2
3
4
5
6
7
8
9
10
scala> (new Container[String]).addIt("123")
res11: Int = 246

scala> (new Container[Int]).addIt(123)
res12: Int = 246

scala> (new Container[Float]).addIt(123.2F)
<console>:8: error: could not find implicit value for evidence parameter of type (Float) => Int
(new Container[Float]).addIt(123.2)
^

<% 除了方法使用之外,class声明类型参数时也可使用:

1
2
scala> class A[T <% Int]
defined class A

但无法对trait的类型参数使用 <%,

1
2
scala> trait A[T <% Int]
<console>:1: error: traits cannot have type parameters with context bounds `: ...' nor view bounds `<% ...'

广义类型约束符号 =:=, <:<, <%<

方法可以通过隐式参数执行更复杂的类型限制。例如,List 支持对数字内容执行 sum,但对其他内容却不行。可是 Scala 的数字类型并不都共享一个超类,所以我们不能使用T <: Number。相反,要使之能工作,Scala的math库对适当的类型T 定义了一个隐含的 Numeric[T]。 然后在 List 定义中使用它:

1
sum[B >: A](implicit num: Numeric[B]): B

如果你调用 List(1,2).sum(),你并不需要传入一个 num 参数;它是隐式设置的。但如果你调用 List("whoop").sum(),它会抱怨无法设置 num。

在没有设定陌生的对象为 Numeric 的时候,方法可能会要求某种特定类型的“证据”。这时可以使用以下类型-关系运算符:

A =:= B A 必须和 B相等
A <:< B A 必须是 B的子类
A <%< B A 必须可以被看做是 B

(如果你在尝试使用 <:< 或者 <%< 的时候出错了,那请注意这些符号在 Scala 2.10 中被移除了。Scala School 里的例子仅能在 Scala 2.9.x 下正常工作。你可以使用新版本的 Scala,但可能会遇到错误。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scala> class Container[A](value: A) { def addIt(implicit evidence: A =:= Int) = 123 + value }
defined class Container

scala> (new Container(123)).addIt
res11: Int = 246

scala> (new Container("123")).addIt
<console>:10: error: could not find implicit value for parameter evidence: =:=[java.lang.String,Int]
// 类似地,根据之前的隐式转换,我们可以将约束放松为可视性:

scala> class Container[A](value: A) { def addIt(implicit evidence: A <%< Int) = 123 + value }
defined class Container

scala> (new Container("123")).addIt
res15: Int = 246

字符串插值 s"$c"

参考官方文档:字符串插值

s 字符串插值器

1
2
3
4
val name="James"
println(s"Hello,$name") //Hello,James 此例中,$name嵌套在一个将被s字符串插值器处理的字符串中。插值器知道在这个字符串的这个地方应该插入这个name变量的值,以使输出字符串为Hello,James。使用s插值器,在这个字符串中可以使用任何在处理范围内的名字。

println(s"1+1=${1+1}") //将会输出字符串1+1=2。任何表达式都可以嵌入到${}中。

f 插值器

在任何字符串字面前加上 f,就可以生成简单的格式化串,功能相似于其他语言中的 printf 函数。当使用 f 插值器的时候,所有的变量引用都应当后跟一个printf-style格式的字符串,如%d。看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
val height=1.9d
val name="James"
println(f"$name%s is $height%2.2f meters tall")//James is 1.90 meters tall f 插值器是类型安全的。如果试图向只支持 int 的格式化串传入一个double 值,编译器则会报错。例如:

val height:Double=1.9d

scala>f"$height%4d"
<console>:9: error: type mismatch;
found : Double
required: Int
f"$height%4d"
^ f 插值器利用了java中的字符串数据格式。这种以%开头的格式在

raw 插值器

除了对字面值中的字符不做编码外,raw 插值器与 s 插值器在功能上是相同的。如下是个被处理过的字符串:

1
2
3
4
5
6
7
8
9
scala>s"a\nb"
res0:String=
a
b
// 这里,s 插值器用回车代替了\n。而raw插值器却不会如此处理。

scala>raw"a\nb"
res1:String=a\nb
// 当不想输入\n被转换为回车的时候,raw 插值器是非常实用的。

Akka 相关特殊字符

send !

1
2
3
4
5
case ArticleBody(uri, body) => //If we get the parsed article back, then we've just parsed it
cacheActor ! SetRequest(uri, body) //Cache it as we just
parsed it
senderRef ! body
context.stop(self)

ask ?

1
val future = pongActor ? "unknown"

参考文章

浅谈 Scala 中下划线的用途

Scala中的下划线到底有多少种应用场景

细数Scala下划线“_”的用法

scala中常用特殊符号

【Scala一】Scala各种符号的含义

Scala各种常见的符号小结

Scala中符号语法糖

scala一些符号含义总结