本书使用的表达式(expression)表示在执行后会返回一个值的代码单元。如果使用大括号将多行代码收集在一起,也可以认为是一个表达式,这称为一个表达式块。
表达式为函数式编程提供了基础,因为利用表达式可以返回数据而不会修改现有的数据(如变量)。这就允许使用不可变数据,这是一个关键的函数式编程概念,说明新数据会存储在新的值中而不是存储在现有的变量中。
如果所有代码可以组织为一组有层次的表达式,包括一个或多个返回值的表达式,使用不可变数据就很自然。表达式的返回值会传递到其他表达式,或者存储到值中。通过减少变量的使用,就可以减少函数和表达式的副作用。换句话说,它们只作用于你提供的输入,除了返回值之外不会影响任何其他数据。
1.表达式
表达式是返回一个值的代码单元。
用表达式定义值和变量
语法:使用表达式定义值和变量
val [: ] =
var [: ] =
表达式块
可以使用大括号结合多个表达式创建一个表达式块(expression block)。表达式有自己的作用域,可以包含表达式块中的局部值和变量。块中最后一个表达式将作为整个表达式块的返回值。
val amount = {
| val x = 5 * 20
| x + 10
|}
表达式块可以嵌套,每一级表达式都有自己的值和变量。
{val a = 1; {val b = a * 2; {val c = b + 4; c }}}
语句
语句(statement)就是不返回值的表达式。语句的返回类型为Unit。
例如,下面值定义就是一个语句:
val x = 1
2.if...else表达式块
if表达式
语法:
if()
if (47 % 3 > 0) println("Not a multiple of 3")
if...else表达式
语法:
if ()
else
val max = if (x > y) x else y
3.匹配表达式
匹配表达式(Match expressions)类似Java的“switch”语句,首先会计算一个输入项,并执行第一个“匹配”模式,然后返回它的值。匹配表达式支持一个默认或通配的“全包型”模式。但与Java不同的是 ,只能有0个活1个模式可以匹配,而不会从一个模式“贯穿”到下一个模式,也不需要“break”语句来避免这种贯穿行为。
Scala的匹配表达式极其灵活,允许匹配如类型、正则、数值范围和数据结构内容。
语法如下:
match {
case =>
[case...]
}
例子1:
val x = 10; val y = 20
val max = x > y match {
| case true => x
| case false => y
|}
例子2:
val status = 500
val message = status match {
| case 200 =>
| "ok"
| case 400 => {
| println("ERROR-we called the service incorrectly")
| "error"
| }
| case 500 => {
| println("Error-the service encountered an error")
| "error"
| }
|}
可以用一个模式替换式(pattern alternative)结合多个模式,其中任何一个模式匹配都会触发case块。
语法如下:
case | .. =>
例子:
val day = "MON"
val kind = day match {
| case "MON" | "TUE" | "WED" | "THU" | "FRI" =>
| "weekday"
| case "SAT" | "SUN" =>
| "weekend"
|}
如果找不到与输入表达式匹配的模式,会抛出错误。
为了避免破坏匹配表达式,可以使用一个通配的“全匹配”模式,将通配模式作为最后一个模式放在匹配表达式中,这会匹配所有可能的输入模式。
用通用模式匹配
匹配表达式中可以使用两种通配模式:值绑定和通配符(“下划线”)。
利用值绑定(value binding)或变量绑定(variable binding),将把匹配表达式的输入绑定到一个局部值,然后可以在case块的体中使用。由于模式包含所绑定的值名,并没有要匹配的具体模式,因此值绑定是一个通配模式,能匹配任何输入值。
(1)值绑定
语法:
case =>
例:尝试匹配一个特定字面量,若不匹配则使用值绑定确保能匹配所有其他可能的值:
val message = "ok"
val status = message match {
| case "ok" => 200
| case other => {
| println(s"couldn't parse $other")
| -1
| }
|}
(2)通配符
另一种模式是使用通配符。通配符是一个下划线(_)字符,相当于一个匿名占位符,将在运行时替换为一个表达式的值。
语法:
case _ =>
例子:
val message = "Unauthorized"
val status = message match {
| case "Ok" => 200
| case _ => {
| println(s"couldn't parse $message")
| -1
| }
| }
(3)用模式哨卫匹配
模式哨卫(parttern guard)向值绑定模式增加一个if表达式,从而可以为匹配表达式增加条件逻辑。使用模式哨位时,只有当表达式返回true时模式才匹配。
语法:
case if => <
例子,使用一个模式哨卫区分非null和null响应:
val response: String = null
response match {
| case s if s != null => println(s"Received '$s'")
| case s => println("Error! Received a null response")
| }
Error! Received a null response
(4)用模式变量匹配类型
利用匹配表达式完成模式匹配还有一种方法,即匹配输入表达式的类型。如果匹配,模式变量(Pattern variables)可以把输入值转换为一个不同类型的值,然后可以在case块中使用这个新值和类型。
语法:
case : =>
对于模式变量的命名,除了值和变量的有关命名要求外,唯一的限制是它们必须以一个小写字母开头。
既然所有值都有类型,且它们通常都有很好的描述性,所以可以考虑使用匹配表达式来确定一个值的类型。Scala中支持多态(polymorphic)类型,这就是一个线索,由此可以看出匹配表达式的作用。类型为Int的值可以赋给另一个类型为Any的值。如下例所示,首先创建一个Int,将它赋给一个Any,再使用一个匹配表达式解析它的真正类型:
scala> val x: Int = 12180
x: Int = 12180
scala> val y: Any = x
y: Any = 12180
scala> y match {
| case x: String => s"'x'"
| case x: Double => f"$x%.2f"
| case x: Float => f"$x%.2f"
| case x: Long => s"${x}l"
| case x: Int => s"${x}i"
| }
res1: String = 12180i
尽管为匹配表达式指定的值类型为Any,但是它存储的数据是作为Int创建的。匹配表达式能根据这个值的实际类型来匹配,而不是根据它给定的类型完成匹配。
4.循环
循环(loop)是指反复地执行一个任务,可能包括迭代处理一个数据范围或者一直重复直至一个布尔表达式返回false。
Scala中最重要的循环结构是for循环(for-loop)。可以迭代处理一个数据范围,每次迭代会执行一个表达式,并返回所有表达式返回值的一个集合(可选)。
首先,介绍一个名为Range(范围)的新的数据结构,可以用来迭代处理一个数字序列。需要使用to或null操作符并制定开始和结束证书来创建范围,to操作符会创建一个包含列表(inclusive list),until操作符创建一个不包含列表(exclusive list)。
语法:定义数值范围
[to|until] [by increment]
下面是for循环的基本定义
for ( <- ) [yield] []
yield关键字是可选的。如果表达式中指定了这个关键字,调用的所有表达式的返回值将作为一个集合返回。如果没有指定这个关键字,但是指定了表达式,将会调用这个表达式,但是不能访问它的返回值。
可以用小括号或大括号定义for循环。这两种方式的区别体现在使用多个迭代器时(每行一个迭代器)。如果for循环使用小括号,最后一个迭代器之前的各个迭代器行必须以一个分号结尾。对于使用大括号的for循环,迭代器行末尾的分号是可选的。
例子:迭代处理一周中的7天
scala> for (x <- 1 to 7) { println(s"Day $x:") }
Day 1:
Day 2:
Day 3:
Day 4:
Day 5:
Day 6:
Day 7:
如果实际需要这些消息的一个集合呢?yield关键字可以解决这个问题。
scala> for (x <- 1 to 7) yield { s"Day $x:" }
res4: scala.collection.immutable.IndexedSeq[String] =
Vector(Day 1:, Day 2:, Day 3:, Day 4:, Day 5:, Day 6:, Day 7:)
scala> for (day <- res4) print(day + ",")
Day 1:,Day 2:,Day 3:,Day 4:,Day 5:,Day 6:,Day 7:,
迭代器哨卫
类似匹配表达式中的模式哨卫,迭代器哨卫(iterator guard)也称为过滤器(filter),可以为迭代器增加一个if表达式。使用迭代器哨卫时,可以跳过一次迭代,除非if表达式返回true。
语法:迭代器哨卫
for ( <- if ) ...
例子:创建一个3的倍数的集合
scala> val threes = for (i <- 1 to 20 if i % 3 == 0) yield i
threes: scala.collection.immutable.IndexedSeq[Int] = Vector(3, 6, 9, 12, 15, 18)
迭代器哨卫也可以与迭代器分开,出现在单独的一行上。例如:
scala> val quote = "Faith,Hope,Charity"
quote: String = Faith,Hope,Charity
scala> for {
| t <- quote.split(",")
| if t != null
| if t.size > 0
| }
| { println(t) }
Faith
Hope
Charity
嵌套迭代器
嵌套迭代器时增加到一个for循环的额外的迭代器,迭代总数随迭代器个数倍增。
例子:下面的for循环包含两个迭代器:
//由于两个迭代器的乘积是6次迭代,所以print语句会调用6次
scala> for {
| x <- 1 to 2
| y <- 1 to 3
| }
| { println(s"($x,$y)") }
(1,1)
(1,2)
(1,3)
(2,1)
(2,2)
(2,3)
值绑定
for循环中的一个常见做法是基于当前迭代在表达式块中定义临时值或变量。在Scala中可以在for循环的定义中使用值绑定。
语法:for循环中的值绑定
for ( <- ; = ) ...
例子:对一个Int使用“左移”二进制操作符(<<),来计算2的0次幂到2的8次幂。每个操作的结果绑定到当前迭代的值“pow”:
scala> val powersOf2 = for (i <- 0 to 8; pow = 1 << i) yield pow
powersOf2: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 4, 8, 16, 32, 64, 128, 256)
通过在for循环定义中使用值绑定,可以把循环的大部分逻辑都集中在定义中。可以得到一个更简洁的for循环。
While和Do/While循环
在Scala中没有for循环那么常用,因为它们不是表达式,不能用来获得值。
语法: While循环
while () statement
例子:将一个数反复递减,直到它不再大于0:
scala> var x = 10; while(x > 0) x -= 1
x: Int = 0
//do/while循环:第一次计算布尔表达式前先执行语句。
scala> val x = 0
x: Int = 0
scala> do println(s"Here I am, x = $x") while (x > 0)
Here I am, x = 0
5.小结
编写代码时要考虑使用表达式,甚至要用表达式来建立应用的结构。
要记住一些重要原则:
- 如何将代码组织为表达式
- 表达式如何得到返回值
- 如何处理这个返回值
6.练习
练习(1):
scala> val str = "hello"
str: String = hello
scala> str match {
| case s if s == null => "n/a"
| case s => s
| }
res0: String = hello
练习(2):
使用if...else:
scala> val num = 2.12
scala> if (num > 0) {
| "greater"
| } else if (num == 0) {
| "same"
| } else {
| "less"
| }
使用匹配表达式:
scala> num match {
| case s if s > 0 => "greater"
| case s if s == 0 => "same"
| case s => "less"
| }
res4: String = greater
练习(3):
scala> for (x <- 1 to 100) {
| print(x + ",")
| if (x % 5 == 0) println()
| }
练习(4):
scala> for (x <- 1 to 100) {
| if (x % 3 == 0 && x % 5 != 0) print("type")
| else if (x % 3 != 0 && x % 5 == 0) print("safe")
| else if (x % 3 == 0 && x % 5 == 0) print("typesafe")
| else print(x)
| }