1.条件表达式
Scala的if/else语法结构和Java一样。不过,在Scala中if/else表达式有值,这个值就是跟在if或else之后的表达式的值。例如:
if(x>0) 1 else -1
或者将表达式的值赋值给变量:
val s = if(x>0) 1 else -1
这与如下语句的效果一样:
if(x>0) s = 1 else s = -1
不过,第一种写法更好,因为它可以用来初始化一个val常量。而第二种写法中,s必须是var变量。
--------------------------------
在Scala中if/else有着和Java或者C++的三元表达式相同的功能,例如
x>0 ? 1 : -1 //Java或C++
而在Scala中,我们可以这样:
if(x>0) "hello" else -1
在Scala中每个表达式都有一种类型,表达式的类型可以相同可以不相同,这点与Java有所不同。不同类型表达式的if/else我们称为混合表达式,
接收他们的值或变量的类型为公共类型Any(相当于Java中的Object)
--------------------------------
如果else部分缺失了,例如:
if(x>0) 1
那么可能if没有输出值,但是在Scala中,每个表达式都应该有值。这个问题的解决方案是引入一个Unit类,写做()。不带饿死了的这个if语句等同于
if(x>0) 1 else ()
你可以把()当做“无有用值”的占位符,将Unit当做Java或C++中的void。
注:Scala没有switch语句
注: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中才会有这样的顾虑,其他地方大家大可不必担心
提示:如果想在REPL中粘贴成块的代码又不想让REPL按行执行,可以使用粘贴模式。键入
:paste
把代码粘贴进去,然后Ctrl+D。这样REPL就会把代码块当做一个整体分析。
2.语句终止
在Java和C++中,每个语句都要以分号结束。而现在Scala中与JavaScript和其他脚本类似,行位不需要设置分号。同样在}、else以及类似的位置也不必写分号,只要能够从上下文明确的判断出这里语句是终止的即可。
不过,如果你想在单行中写下多个语句,就需要将他们以分号分开。例如:
if(n > 0) { 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++的程序员一开始并不适应省去分号的做法。如果你更倾向于使用分号,用了就是了,没啥坏处。
3.块表达式和赋值
在Java或C++中,块语句是包含于{}中的语句序列。每当你需要在逻辑分支或循环中放置多个动作时,你都可以使用块语句。
在Scala中,{}块包含一系列的表达式,其中结果也是一个表达式。块中最后一个表达式的值就是块儿的值。
--------------------------------
在Scala中,赋值动作本身是没有值得——或者,更严格地说,他们的值是Unit类型的。你应该记得,Unit类型等同于Java和C++中的void,而这个类型的值只有一个值,写做()。
一个赋值语句结束的块,比如
{r = r * n; n -= 1}
的值是Unit类型的。这没有问题,只是当我们定义函数时需要意识到这一点。
注:由于赋值语句的值是Unit类型的,别把他们串接在一起。
x = y = 1 //别这么做
y = 1 的值是(),你不可能想把一个Unit类型的值赋值给x。(在Java或者C++中,上面的做法是可以做到x、y同时赋值的)
4.输入和输出
如果要打印一个值,我们用print或者println以及printf函数。与Java中的用法相同,这里不做过多解释。
--------------------------------
你可以用readLine函数从控制台读取一行输入。如果要读取数字、Boolean或者是字符,可以用readInt、readDouble、readByte、readShort、readLong、readFloat、readBoolean或者readChar。
与其他方法不同,readLine带一个参数作为提示字符串:
5.循环
Scaca中拥有与Java和C++相同的while和do循环。例如:
--------------------------------
Scala中没有与Java相同的for结构:for(初始化变量;检查变量是否满足条件;更新变量)。
在Scala中只有这样的for循环语法:
for(i ← 表达式) //i不需要定义,也可以所以取名
接下来我们用RichInt类的to方法,去演示Scala中for的用法:
还有另一种遍历方法until,它常用于遍历字符串或者数组,同上面的to方法的例子比较,能看出他们两个的区别
to方法:包含头和尾的遍历(相当于<=)
until方法:包含头不包含尾的遍历(相当于<)
注:遍历字符串的两种方法:
1.用until方法
2.直接遍历
--------------------------------
Scala中并没有提供break或者continue语句来退出循环。如果需要break时我们可以采取如下选项:
- 使用Boolean型控制变量
- 使用嵌套函数——你可以从函数当中return
- 使用Breaks对象中的break方法
break方法使用示例:
6.高级for循环和for推导式
Scala中的for循环要比Java功能丰富的多,比如:
1.我们可以在for循环中添加多个 变量←表达式 这种形式的生成器,用分号分隔开:
2.每一个生成器都可以带一个守卫,以if开头的Boolean表达式:注意if前没有分号
3.你可以使用任意多的定义,引入可以在循环中使用的变量
4.如果for循环以yield开始,则该循环会构造出一个集合,每次迭代生成集合中的一个值
这类循环叫做for推导式,for推导式生成的集合与它的第一个生成器的类型是兼容的
7.函数
Scala除了方法还支持函数,方法对对象进行操作,函数不是。C++也有函数,在Java中我们只能通过静态方法来模拟函数。
要定义函数,完整格式如下:
def [函数名称]([参数名称]:[参数类型]...): [返回值类型] = {
函数体
}
例如:
def add(x : Int , y : Int) : Int = {
var z = x + y;
return z;
}
由于{}块中最后一个表达式的值就是块儿的值。因此我们可以省略return。
def add(x : Int , y : Int) : Int = {
var z = x + y;
z;
}
在进一步简化:
def add(x : Int , y : Int) : Int = x + y
在Scala中,只要函数不是递归的,我们就不需要指定返回类型。Scala编译器可以通过 = 右侧的表达式类型推断出返回值类型。因此最终函数简化如下:
def add(x : Int , y : Int) = x + y
如果是递归函数,我们就必须要指定返回值类型了。
def fac(n : Int) : Int = if(n <= 0) 1 else n * fac( n - 1)
说明:虽然在带名函数中使用return并没有什么不对,但是我们最好适应没有return的日子。很快,你就会使用大量的匿名函数,这些函数中return并不返回值给调用者。
它跳出到包含它的带名函数中。我们可以把return当做是函数版break语句,尽在需要时使用。
8.默认参数和带名参数
我们在调用某些函数时并不显示的给出所有参数值,对于这些函数我们可以使用默认参数值。例如:
def decorate(prefix : String, str : String, suffix : String) = {
prefix + str + suffix
}
在调用上面的decorate函数的时候我们必须给出所有的参数,否则函数将会执行错误,那如果此时我们只想给出一个参数就能让其运行我们该如何做呢?那就是默认参数值:
def decorate(prefix : String = "[[", str : String, suffix : String = "]]") = {
prefix + str + suffix
}
我们可以在定义函数入参时先给其赋值,从而做到默认参数值的功能,但是另一个问题又来了,作为程序员我们都知道调用方法或者函数的时候,参数都是按顺序写入的,我们如何做到给指定位置的入参赋值呢?Scala帮我们做到了这一点:
如图,我们可以向表达式一样为指定参数赋值,这样我们就可以无视参数的顺序来调用函数或者方法了,这就是带名参数
9.变长参数
我们知道在Java中,边长参数用 ... 来表示:
public void method(int a ...){
方法体...
}
而在Scala中,边长参数用* 来表示,比如我们创建一个计算多个数相加的方法,参数不确定。
def add(a : Int*) = {
var sum = 0;
for(x <- a) sum += x;
sum;
}
对于上面的add方法,如果你已经有了一个有值序列,则不能直接将它传入上面的大的方法,比如:
val a = add(1 to 5) //错误
因为我们的add方法的入参是Int类型的序列,而1 to 5 的类型为:
他并不是add方法的入参类型因此直接传入一定有问题,需要进行类型的转换,那么范围类型如何转换成入参序列呢?Scala中为我们提供了一种语法,追加 :_*
val a = add(1 to 5 :_* )
两种情况对比:
在递归的定义中我们会用到上述语法:
def recursiveSum(args : Int*): Int = {
if(args.length == 0) 0
else args.head = recursiveSum(args.tail:_*)
}
在这里,序列的head是他的首个元素,而tail是所有其他元素的序列,这又是一个序列,我们用 :_* 来将它转换成参数序列。
10.过程
Scala对于不返回值得函数有特殊的表示法。如果函数体包含在花括号当中但没有前面的=号,那么返回类型就是Unit。这样的函数被称为过程。过程不返回值,我们调用它仅仅是为了他的辅助作用。
例如:
def out(a : Int){
print(a)
}
或者我们可以显示声明Unit返回类型:
def out(a : Int): Unit = {
print(a)
}
11.懒值
当val 被声明为lazy时,他的初始化将被延迟,也就是懒加载,知道我们首次对它进行取值,例如
//在words被定义时取值
val words = scala.io.Source.fromFile("C:\\Users\\Administrator\\Desktop\\wordCount.txt").mkString
//在words被首次使用时取值
lazy val words = scala.io.Source.fromFile("C:\\Users\\Administrator\\Desktop\\wordCount.txt").mkString
//在每一次words被使用时取值
def words = scala.io.Source.fromFile("C:\\Users\\Administrator\\Desktop\\wordCount.txt").mkString
你可以把懒值当做是介于val和def的中间状态
--------------------------------
说明:懒值并不是没有额外开销。我们每次访问懒值,都会有一个方法被调用,而这个方法将会以线程安全的方式检查该值是否已被初始化。
12.异常
Scala的异常工作机制和Java或者C++一样。抛出异常时你可以这样:
throw new Exception("this is a exception")
和Java一样,排出的对象必须是java.lang.Throwable的子类。不过,与Java不同的是,Scala没有“受检”异常,你不需要声明说函数或方法可能抛出某种异常。
--------------------------------
throw表达式有特殊的返回类型Nothing。比如:
if(x >= 0){
sqrt(x)
}else throw new IllegalArgumentException(" x should not be negative")
第一个分支类型为Double,第二个分支类型为Nothing。因此,if/else表达式的类型是Double
--------------------------------
捕获异常的语法采用的是模式匹配的语法
try{
"Hello".toInt
} catch {
case _ : Exception => println("xxxxxxx")
case ex : IOException => ex.printStackTrace()
}
和Java或C++一样,更通用的异常应该排在更具体的异常之后。
如果你不需要使用捕获的异常对象,可以使用_来代替变量名。
--------------------------------
try/finally语句让你可以释放资源,不论有没有发生异常。这点和Java一样。
我们也可以下车和Java中一样的try/catch/finally语句:
try{...}catch{...}finally{...}