全部博文(465)
分类: Java
2012-11-06 09:01:25
条件表达式
Scala的if/else语法结构和Java或C++一样。不过,在Scala中if/else表达式有值,这个值就是跟在if或else之后的表达式的值。例如:
if (x > 0) 1 else -1
上述表达式的值是1或−1,具体是哪一个取决于x的值。你可以将if/else表达式的值赋值给变量:
val s = if (x > 0) 1 else -1
这与如下语句的效果一样:
if (x > 0) s = 1 else s = -1
不过,第一种写法更好,因为它可以用来初始化一个val。而在第二种写法当中,s必须是var。
(之前已经提过,Scala中的分号绝大多数情况下不是必需的。)
Java和C++有一个?:操作符用于同样目的。如下表达式
x > 0 ? 1 : -1 // Java或C++
等同于Scala表达式 if (x > 0) 1 else −1。不过,你不能在?:表达式中插入语句。Scala的if/else将在Java和C++中分开的两个语法结构if/else和?:结合在了一起。
在Scala中,每个表达式都有一个类型。举例来说,表达式 if (x > 0) 1 else −1的类型是Int,因为两个分支的类型都是Int。混合类型表达式,比如:
if (x > 0) "positive" else -1
上述表达式的类型是两个分支类型的公共超类型。在本例中,其中一个分支是java.lang.String,而另一个分支是Int。它们的公共超类型叫做Any。(详细内容参见8.11节。)
如果else部分缺失了,比如:
if (x > 0) 1
那么有可能if语句没有输出值。但是在Scala中,每个表达式都应该有某种值。这个问题的解决方案是引入一个Unit类,写做()。不带else的这个if语句等同于
if (x > 0) 1 else ()
你可以把()当做是表示“无有用值”的占位符,将Unit当做Java或C++中的void。(从技术上讲,void没有值但是Unit有一个表示“无值”的值。如果你一定要深究的话,这就好比空的钱包和里面有一张写着“没钱”的无面值钞票的钱包之间的区别。)
说明:Scala没有switch语句,不过它有一个强大得多的模式匹配机制,我们将在第14章中看到。在现阶段,用一系列的if语句就好。
注意:REPL比起编译器来更加“近视”——它在同一时间只能看到一行代码。
举例来说,当你键入如下代码时:
if (x > 0) 1
else if (x == 0) 0 else -1
REPL会执行 if (x > 0) 1,然后显示结果。之后它看到接下来的else关键字就会不知所措。
如果你想在else前换行的话,用花括号:
if (x > 0) { 1
} else if (x == 0) 0 else -1
只有在REPL中才会有这个顾虑。在被编译的程序中,解析器会找到下一行的else。
提示:如果你想在REPL中粘贴成块的代码,而又不想担心REPL的近视问题,可以使用粘贴模式。键入:
:paste
把代码块粘贴进去,然后按下Ctrl+D。这样REPL就会把代码块当做一个整体来分析。
语句终止
在Java和C++中,每个语句都以分号结束。而在Scala中——与JavaScript和其他脚本语言类似——行尾的位置不需要分号。同样,在}、else以及类似的位置也不必写分号,只要能够从上下文明确地判断出这里是语句的终止即可。
不过,如果你想在单行中写下多个语句,就需要将它们以分号隔开。例如:
if (n > 0) { r = r * n; n -= 1 }
我们需要用分号将 r = r * n 和 n -= 1 隔开。由于有},在第二个语句之后并不需要写分号。
如果你在写较长的语句,需要分两行来写的话,就要确保第一行以一个不能用做语句结尾的符号结尾。通常来说一个比较好的选择是操作符:
s = s0 + (v - v0) * t + // +告诉解析器这里不是语句的末尾
0.5 * (a - a0) * t * t
在实际编码时,长表达式通常涉及函数或方法调用,如此一来你并不需要过分担心——在左括号(之后,编译器直到看到匹配的)才会去推断某处是否为语句结尾。
正因如此,Scala程序员们更倾向于使用Kernighan & Ritchie风格的花括号:
if (n > 0) {
r = r * n
n -= 1
}
以{结束的行很清楚地表示了后面还有更多内容。
许多来自Java或C++的程序员一开始并不适应省去分号的做法。如果你倾向于使用分号,用就是了——它们没啥坏处。
块表达式和赋值
在Java或C++中,块语句是一个包含于{ }中的语句序列。每当你需要在逻辑分支或循环中放置多个动作时,你都可以使用块语句。
在Scala中,{ }块包含一系列表达式,其结果也是一个表达式。块中最后一个表达式的值就是块的值。
这个特性对于那种对某个val的初始化需要分多步完成的情况很有用。例如,
val distance = { val dx = x - x0; val dy = y - y0; sqrt(dx * dx + dy * dy) }
{ }块的值取其最后一个表达式,在此处以粗体标出。变量dx和dy仅作为计算所需要的中间值,很干净地对程序其他部分而言不可见了。
在Scala中,赋值动作本身是没有值的——或者,更严格地说,它们的值是Unit类型的。你应该还记得,Unit类型等同于Java和C++中的void,而这个类型只有一个值,写做()。
一个以赋值语句结束的块,比如
{ r = r * n; n -= 1}
的值是Unit类型的。这没有问题,只是当我们定义函数时需要意识到这一点。
由于赋值语句的值是Unit类型的,别把它们串接在一起。
x = y = 1 // 别这样做
y = 1的值是(),你几乎不太可能想把一个Unit类型的值赋值给x。(与此相对应,在Java和C++中,赋值语句的值是被赋的那个值。在这些语言中,将赋值语句串接在一起是有意义的。)
输入和输出
如果要打印一个值,我们用print或println函数。后者在打印完内容后会追加一个换行符。举例来说,
print("Answer: ")
println(42)
与下面的代码输出的内容相同:
println("Answer: " + 42)
另外,还有一个带有C风格格式化字符串的printf函数:
printf("Hello, %s! You are %d years old.\n", "Fred", 42)
你可以用readLine函数从控制台读取一行输入。如果要读取数字、Boolean或者是字符,可以用readInt、readDouble、readByte、readShort、readLong、readFloat、readBoolean或者readChar。与其他方法不同,readLine带一个参数作为提示字符串:
val name = readLine("Your name: ")
print("Your age: ")
val age = readInt()
printf("Hello, %s! Next year, your will be %d.\n", name, age + 1)
本文节选自《快学Scala》
电子工业出版社出版
(美)霍斯曼(Horstmann,C.S.)著
高宇翔译