一、Scala开发环境
搭建Scala 开发环境,一是在IntelliJ IDEA 上安装Scala 插件和安装Scala SDK,具体操作步骤可以参考Scala专栏文章。
二是通过Scala RELP(Read-Eval-Print Loop)交互式环境,该交互式环境适合代码简单调试,不太适合进行应用开发。
二、变量和函数
定义变量时没有指定变量类型。这是否意味着 Scala 是和 Python 或者 Ruby 一样的动态类型语言呢?恰恰相反,Scala 是严格意义上的静态类型语言,由于其采用了先进的类型推断(Type Inference)技术,程序员不需要在写程序时显式指定类型,编译器会根据上下文推断出类型信息。比如变量 x被赋值为 0,0 是一个整型,所以 x的类型被推断出为整型。当然,Scala 语言也允许显示指定类型,如变量 x1,y1的定义。一般情况下,我们应尽量使用 Scala 提供的类型推断系统使代码看上去更加简洁。
另一个发现是程序语句结尾没有分号,这也是 Scala 中约定俗成的编程习惯。大多数情况下分号都是可省的,如果你需要将两条语句写在同一行,则需要用分号分开它们。
函数的定义也非常简单,使用关键字 def,后跟函数名和参数列表,如果不是递归函数可以选择省略函数返回类型。Scala 还支持定义匿名函数,匿名函数由参数列表,箭头连接符和函数体组成。函数在 Scala 中属于一级对象,它可以作为参数传递给其他函数,可以作为另一个函数的返回值,或者赋给一个变量。在下面的示例代码中,定义的匿名函数被赋给变量 cube。匿名函数使用起来非常方便,比如 List对象中的一些方法需要传入一个简单的函数作为参数,我们当然可以定义一个函数,然后再传给 List对象中的方法,但使用匿名函数,程序看上去更加简洁。
// 定义函数
def square(x: Int): Int = x * x
// 如果不是递归函数,函数返回类型可省略
def sum_of_square(x: Int, y: Int) = square(x) + square(y)
sum_of_square(2, 3)
// 定义匿名函数
val cube = (x: Int) => x * x *x
cube(3)
// 使用匿名函数,返回列表中的正数
List(-2, -1, 0, 1, 2, 3).filter(x => x > 0)
让我们再来和 Java 中对应的函数定义语法比较一下。首先,函数体没有像 Java 那样放在 {}里。Scala 中的一条语句其实是一个表达式,函数的执行过程就是对函数体内的表达式的求值过程,最后一条表达式的值就是函数的返回值。如果函数体只包含一条表达式,则可以省略 {}。其次,没有显示的 return语句,最后一条表达式的值会自动返回给函数的调用者。
和 Java 不同,在 Scala 中,函数内部还可以定义其他函数。比如上面的程序中,如果用户只对 sum_of_square 函数感兴趣,则我们可以将 square 函数定义为内部函数,实现细节的隐藏。
定义内部函数:
三、流程控制语句
复杂一点的程序离不开流程控制语句,Scala 提供了用于条件判断的 if else和表示循环的 while。和 Java 中对应的条件判断语句不同,Scala 中的 if else是一个表达式,根据条件的不同返回相应分支上的值。比如下面例子中求绝对值的程序,由于 Scala 中的 if else是一个表达式,所以不用像 Java 那样显式使用 return返回相应的值。
使用 if else 表达式:
def abs(n: Int): Int = if (n > 0) n else -n
和 Java 一样,Scala 提供了用于循环的 while 语句,在下面的例子中,我们将借助 while 循环为整数列表求和。
使用 while 为列表求和:
def sum(xs: List[Int]) = {
var total = 0
var index = 0
while (index < xs.size) {
total += xs(index)
index += 1
}
total
}
上述程序是习惯了 Java 或 C++ 的程序员想到的第一方案,但仔细观察会发现有几个问题:首先,使用了 var定义变量,我们在前面说过,尽量避免使用 var。其次,这个程序太长了,第一次拿到这个程序的人需要对着程序仔细端详一会:程序首先定义了两个变量,并将其初始化为 0,然后在 index小于列表长度时执行循环,在循环体中,累加列表中的元素,并将 index加 1,最后返回最终的累加值。直到这时,这个人才意识到这个程序是对一个数列求和。
让我们换个角度,尝试用递归的方式去思考这个问题,对一个数列的求和问题可以简化为该数列的第一个元素加上由后续元素组成的数列的和,依此类推,直到后续元素组成的数列为空返回 0。具体程序如下,使用递归,原来需要 9 行实现的程序现在只需要两行,而且程序逻辑看起来更清晰,更易懂。
使用递归对数列求和:
//xs.head 返回列表里的头元素,即第一个元素
//xs.tail 返回除头元素外的剩余元素组成的列表
def sum1(xs: List[Int]): Int = if (xs.isEmpty) 0 else xs.head + sum1(xs.tail)
有没有更简便的方式呢?答案是肯定的,我们可以使用列表内置的一些方法达到同样的效果:
上述使用了规约操作。
规约操作是对容器的元素进行两两运算,将其规约为一个值。最常见的规约方式使 reduce,它接受一个二元函数 f 作为参数,首先将 f 作用在某两个元素上并返回一个值,然后再将 f 作用在上一个返回值和容器的下一个元素上,再返回一个值,依次类推,最后容器中的所有值会被规约为一个值。
list map (_.toString) reduce((x,y)=>s"f($x,$y)")
上面这行代码:先通过 map 操作将List[Int] 转化成 List[String],也就是把列表中的每个元素从 Int 类型转换成 String 类型,然后对这个字符串进行自定义规约,语句的执行结果清楚地展示了 reduce的过程。
事实上,List 已经为我们提供了 sum 方法,在实际应用中,我们应该使用该方法,而不是自己定义一个。作者只是希望通过上述例子,让大家意识到 Scala 虽然提供了用于循环的 while 语句,但大多数情况下,我们有其他更简便的方式能够达到同样的效果。
四、如何运行 Scala 程序?
在运行方式上,Scala 又一次体现出了它的灵活性。它可以被当作一种脚本语言执行,也可以像 Java 一样,作为应用程序执行。
作为脚本执行:
我们可以将 Scala 表达式写在一个文件里,比如 Hello.scala。在命令行中直接输入 scala Hello.scala就可得到程序运行结果。
Hello.scala 代码:
println("Hello Rickie!")
作为应用程序执行:
作为应用程序执行时,我们需要在一个单例对象中定义入口函数 main,经过编译后就可以执行该应用程序了。
object HelloRickie {
def main(args: Array[String]): Unit = {
println("Hello Rickie!")
}
}
Scala 还提供了一个更简便的方式,直接继承另一个对象 App,无需定义 main方法,编译即可运行。
五、结束语
本文为大家介绍了 Scala 的基本语法,相比 Java,Scala 的语法更加简洁,比如 Scala 的类型推断可以省略程序中绝大多数的类型声明,短小精悍的匿名函数可以方便的在函数之间传递,还有各种在 Scala 社区约定俗成的习惯,比如省略的分号以及函数体只有一条表达式时的花括号,这一切都帮助程序员写出更简洁,更优雅的程序。