工作如何繁忙,生活如何糟心,至少求知的这一刻是我的…
前言
基于Scala 2.12.14。学的《Scala for the Impatient》,记录每天的笔记,有些名词看的英文,翻得未必准确。
适用对象:Java程序员。尽可能在短时间内建立起Java到Scala的转换,做到能看能写。实际上在学习一门语言,应当逐步抛弃掉固有成见,才能发现其中乐趣。人生苦短,别用Java:(逃。
今日重点
了解Scala中的程序控制流,包含有:
- 条件表达式
- 代码块
- 用户输入输出
- 循环体
条件表达式
在Scala中,条件表达式后往往跟着赋值语句,判定条件的语法和Java是一致的,但紧接着给出条件表达式的值,等价于"foo == bar ? v0: v1"三元运算:
if (0 < x) 1 else -1
因此可以写成:
// 推荐使用这种方式,v可以按val初始化
val v = if (0 < x) 1 else -1
等价于:
// 这就和Java的语法一样了
// v只能被申明为var
if (0 < x) v = 1 else v = -1
在Scala中,分支的表达式的值是有类型的:
// 因为所有分支的表达式的值的类型都是Int,所以该表达式的类型是Int
if (0 < x) 1 else -1
// 表达式的值类型有Int有String,该表达式的类型是这两个类的共同父类,也就是Any
if (0 < x) "str" else -1
省略else的语法示例如下:
if (0 < x) 1
如果期望特定分支没有值呢?
// ()实际对应了Scala中的Unit,可以看作是Java中的void
// 上边忽略else语句,可等价于:
if (0 < x) 1 else ()
Scala中是没有Java中的switch分支的,不是重要语法。
交互式命令行是一次读取一行并执行,试试这个:
scala> if (0 < x) 1
res1: AnyVal = 1
scala> else if (x < 0) -1 else -2
<console>:1: error: illegal start of definition
else if (x < 0) -1 else -2
^
想想Java是怎么做的?
scala> val x = 1
x: Int = 1
scala> if (0 < x) {
1 } else if (x < 0) -1 else -2
res1: Int = 1
是的,使用{}指明这是一个语句块,并且让这个语句块跨行。接着我们继续看代码块。
代码块
在Java中,我们常使用";"语法代表一条语句的结束,但在Scala中并非必须,只有在一行有多条语句时才有必要分隔。
if (x < 0) foo += 1; bar -= 1
没有闭合的语句可以让编译器/即时交互命令行读取跨行的表达式。没写完的表达式:
var sum = v0 + v1 *
v2 - v3
if (0 < x) { // 未闭合
sum = v0 + v1
avg = sum / 2
} // 闭合
可以看到Scala的代码块和Java很相似,但Scala的代码块是有值的,并可被赋值给变量:
val foo = {
val bar = 1
// 最后一个值即foo的值
1
}
要注意,赋值/初始化语句是没有值的,看一个示例:
val foo = {
val bar = 1
// foo = () 即 Unit
}
因为赋值语句没有值,因此链式的赋值语句是错误的,即使Java中可以这么使用:
val x = y = 1
// 一步步拆开看:
// y = 1的值是()
// x = ()
用户输入输出
打印输出
java中常见的print和println都有,只不过是我们在scala中直接可调用这两个函数:
print("hello ")
println("world")
// c-like
printf("Hello %s, %d%n", "scala", 2021)
// 也可以,$取相应的变量值,像perl, shell...
// 但Scala更高级,$还可以带表达式
print(f"Hello, $scala! Now is ${year + 1 - 0.5 * 2}%4.1f.%n")
推荐使用f插值器的方式,即print(f"string"),它可以支持表达式,支持c-like的占位符,更重要的是,它是类型安全的,如果使用了特定类型的占位符,但其值不是这种类型,那么在编译期就会抛错。如%f,但表达式的值不是数值。
在Scala中,插值器还有s和raw。s插值器允许使用字符串表达式;raw表达式则是对值进行自动转义。
scala> raw"\n is a newline"
res8: String = \n is a newline
scala> "\n is a newline"
res9: String =
"
is a newline"
scala> s"$$$year"
res10: String = $2021
scala> s"$$year"
res11: String = $year
scala> s"$year"
res12: String = 2021
// $取值,但$$会转义
用户输入
读取输入,可以使用scala.io.StdIn类中的方法,包含有:
-
readInt
-
readDoube
-
readByte
-
readShort
-
readLonf
-
readFloat
-
readBoolean
-
readChar
-
readLine
方法名望文生义。
scala> import io.StdIn._
import io.StdIn._
scala> val x = readInt() // << 11
x: Int = 11
scala> val y = readLine("y = ") // << 22
y = y: String = 22 // readLine读取的是String
循环体
while
和Javaer常用的while是一样的,不用示例了吧。
for
for循环和Javaer常用的for(initialize; test; update)语法不一致,但和for(value: iterable)是很像的,Scala的语法是:
for(i <- 1 to n)
v += 1
// 一步步拆解开:
// 1 to n,还记得Day0的内容吗?实际上是由RichInt提供的方法,它将返回[1, n]的序列
// <-,则是Scala表示遍历将表达式的值,但值应为Scala中的集合。示例中的是Range。
Scala中for语法总结出也就是for(i <- expr)。
字符串怎么遍历呢?
val s = "hello"
for (i <- 0 to s.length - 1)
print(s(i))
// 也可以如此
for (c <- s)
print(c)
break和continue
在循环体控制流中重要的break和continue语法决定了我们编写程序的方式,但在Scala中是没有这两种控制语法的。有替代方法吗?——有的:
// break替代
import util.control.Breaks._
breakable {
for(...) {
if (...) break // break是函数,它将跳到breakable标记的语句块外。
}
}
// break到这里执行
但对于性能要求比较高的场景,不适合使用这种方法,因为它实际上是通过抛出异常并捕获实现的。JVM中抛出异常会发生什么呢?自然是完整地收集异常栈信息,这可就太慢啦。
还有别的方式吗?自然是使用布尔值决定循环制流的走向。还有种是通过嵌套函数的方式,将循环封装成一个函数,在特定条件下return。
Scala循环的高级特性
笛卡尔积
Scala允许在循环体中应用多个generator,也就是多个variable <- expression:
for (i <- 1 to 3; j <- 1 to 3) print(f"${10 * i + j}%3d")
// 11 12 13 21 22 23 31 32 33
generator应用条件表达式
每个generator都可以应用一个条件表达式:
for (i <- 1 to 3; j <- 1 to 3 if i != j) print(f"${10 * i + j}%3d")
// 12 13 21 23 31 32
generator应用变量
generator可以使用变量,实际上也不算特性,循环本身就可以使用变量,只不过是在某个generator中定义的变量的作用域是支持到其他generator使用的:
for (i <- 1 to 3; from = 4 - i; j <- from to 3) print(f"${10 * i + j}%3d")
// 13 22 23 31 32 33
循环生成集合
循环体的表达式是可以有值的,可以通过yield关键字构造表达式的值为集合:
scala> for (i <- 1 to 10) yield i % 3
res17: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 0, 1, 2, 0, 1, 2, 0, 1)
生成的集合中的类型取决于第一个generator:
scala> for (ch <- "hello"; i <- 0 to 1) yield (ch + i).toChar
res20: String = hieflmlmop
scala> for (i <- 0 to 1; ch <- "hello") yield (ch + i).toChar
res21: scala.collection.immutable.IndexedSeq[Char] = Vector(h, e, l, l, o, i, f, m, m, p)
茴的四种写法
for循环结构可跨行:
for {
i <- 1 to 3
j = 3 - i
}
// 等价于
for (i <- 1 to 3; j = 3 - i)