首先需要Scala开发环境的搭建,网上自己找教程。
声明常量与变量
val foo = 0 // 常量 var bar = 0 // 变量
在Scala中,更加鼓励使用val来进行声明,也就是推荐使用常量。在大部分时候不需要分号,看自己便好…虽然是强静态类型的语言,但是却并没有写出类型——这就是类型推断。
也可以指定类型:
val spark: String = "Hello Scala"
多个的同时声明:
val xInt, yInt = 100 // xInt, yInt都声明为100
常用类型
与Java类似,Scala中也有表示数值类型的类,但是Scala不区分基本类型和引用类型。也就是说Scala中一切都是对象了吧。
Scala对Java中的数值类型的功能添加了许多功能上的强化,这其中涉及了隐式转换。比如,String类型转换成StringOps类型。还有RichInt、RichDouble等对应Int、Double类型。至于有什么新加的方法,可以查看Scala API。
操作符
算数操作符应该与预期中的没有什么差异,因为如果有差异用起来就有麻烦了。但算数操作符事实上是方法。
a + b 相当于 a.+(b)
对于带有一个隐式参数和一个显式参数的方法来说,通常都可以这么简化。如:
1.to(10) 这会产生一个1-10的Range实例,相当于 1 to 10
两种风格任君选择。
Scala没有提供++和–(别认为我打错了,这里是有两个-号的)这两个其他语言中很常见常用的操作符,理由是:不值得为少按一个键而额外增加一个特例。
Scala中可以对操作符进行重载。
调用函数和方法
函数(function)和方法(method)
引入相应的包后,使用函数很简单,不需要从某个类中调用静态方法。
math.sqrt(2) 等同于 scala.math.sqrt(2) // 以scala.开头的包名,都可以省去该前缀
Scala中没有静态方法,但是有单例对象和伴生对象之类的概念可以实现差不多的特性,这些估计到后面会细说。
BigInt.probablePrime(100, util.Random)
通常,使用没有参数,且不改变当前对象的方法时,不带圆括号。
apply方法
s是一个字符串,而 s(4) 将会返回该字符串中第4个字符。在使用形如 s(4) 这种,类似于函数调用的语法时,实际上是调用了 apply() 方法。也就是说, s(4) 等于 s.apply(4)
两个容易混淆的概念——表达式(expression)和语句(statement)。表达式有值,而语句没有值只执行操作。
要说起这一点的原因是,Scala中,几乎所有构造出来的语法结构都有值,也就是表达式。这可以使程序更加精简易读。
举例,if表达式是有值的;语句块也是有值的,是最后一个表达式的值。
而对应Java中的void,Scala中有一个类型Unit,用来表示“无有用值”(void表示没有值,这里还是有差别的)。
条件表达式
if/else表达式在Scala中是有值的,除开这一点,与Java等语言没有什么差别。而有值所带来的效果,可以是下面这样子的:
val s = if (x > 0) 1 else -1 // 与上等效的另一种写法 if (x > 0) s = 1 else s = -1
第一种写法可以用来初始化一个val,而第二种则要求s必须是var。前面也说了,Scala中更鼓励使用val。
同时,这样的条件表达式,还可以实现比三目操作符 ? : 更加灵活的功能。
如果在if和else两边的类型是不同的,比如:
if (x > 0) "positive" else -1
这时返回的类型将会是两个分支类型的公共超类型(这里是String和Int的公共超类型Any)。
如果没有else部分,那么if表达式有可能不返回值,这时候就需要前面提到的Unit类型,写做()。
if (x > 0) 1 // 这相当于else部分返回Unit类型,相当于下面的写法 if (x > 0) 1 else ()
语句终止
分号不是必须的,但是使用它们不会带来坏处。
能根据上下文判断出语句结尾的地方,都不需要使用分号。怎么判断是否语句结尾,以后在编码过程中会慢慢积累起感觉的。
如果需要在单行中写多个语句,那么就需要使用分号了。而在写一个较长的语句时,如果需要分行写,要确保前面的行尾需要以不能用来语句结尾的符号来结束。比如说像是下面的操作符:
a = s0 + (v - v0) * t + // +说明了这里不是语句结束哦 0.5 + (a - a0) * t * t // 下面这种写法就会出问题了 a = s0 + (v - v0) * t + 0.5 + (a - a0) * t * t
这里还可以发现的是,Scala推荐使用两个空格来缩进。
由于判断语句终止的需要,Scala程序员更倾向于使用Kernighan & Ritchie风格的花括号(也就是Java中推荐的方法):
if (n > 0) { r = r * n n -= 1 }
块表达式和赋值
Scala中,{ }包含一系列的表达式,而且{ }最后返回的也是一个表达式——最后一个表达式。
而赋值语句的值,是Unit类型的。这也大体上决定了在Java和C++中的 x = y = 1 会出现的赋值语句,几乎不可能出现在Scala中。
输入和输出
输入函数: readLine()、 readInt()等,用来从控制台Console中读取输入。
而输出部分,则是很常见的 print()、 println()、 printf()。
循环
看看Scala中的for循环:
for (i <- expression) // 让i遍历表达式的所有值,遍历的执行取决于表达式的类型 // 例子 for (i <- 1 to n) // 遍历1到n for (i <- 0 util s.length) // 遍历0到s.length-1 // 遍历求和 var sum = 0 for (ch <- "Hello") sum += ch
事实上,Scala中更多是使用函数式而不是指令式的风格来遍历等操作。后面会见到的。
高级for循环和for推导式
在一个for循环中使用多个“generator”(生成器):
for (i <- 1 to 3; j <- 1 to 3) print ((10 * i +j) + " " // 结果:11 12 13 21 22 23 31 32 33
在for循环中使用“filter ”(过滤器,守卫)来过滤一部分不符合条件的枚举情况:
for (i <- 1 to 3; j <- 1 to 3 if i != j) print ((10 * i + j) + " ") // 结果:12 13 21 23 31 32
可以使用多个filter,中间需要使用分号分隔。
可以在for表达式中使用任意多的定义,引入在循环中使用变量:
for (i <- 1 to 3; from = 4 - i; j <- from to 3) print ((10 * i + j) + " ") // 结果:13 22 23 31 32 33
如果for表达式的循环体是以yield关键词开始的,那么该循环会构造出一个集合,这被称为for推导式。推导式生成的集合与第一个generator是类型兼容的。
for (c <- "Hello"; i <- 0 to 1) yield (c + i).toChar // 生成"HIeflmlmop"
还可以将for表达式的小括号换成花括号,这样可以省去写分号的功夫。
for表达式内容不少,而且这里也不是全部。在《Programming in Scala》中,还有一些高级的讨论,这里就先略了,先入个门。
函数
在Java中并没有函数这个概念,函数的功能是用静态方法来实现的。
函数的定义:
def 函数名(指明类型的参数): 可选的返回值类型 = 函数体 def fac(n: Int) = { var r = 1 for (i <- 1 to n) r = r * i r }
只要函数不是递归的,就不需要给出函数的返回值类型(因为有类型推断)。一个递归的函数需要如下写:
def fac(n: Int): Int = if (n <= 0) 1 else n * fac(n - 1)
在Scala中,并不时常使用return来返回值。
默认参数和带名参数
Java里似乎没有默认参数和带名参数,至少我从来没有使用过。但记得C++是有的。
def decorate(str: String, left: String = "[", right: String = "]") = left + str + right
上面的函数有两个带有默认值的参数left和right。如果不给出这两个参数,调用这个函数,会使用默认值。也可以给出自己的参数来替代默认的。
参数个数不够时,将会从后往前使用默认参数。
调用函数时,如果指定参数名,那么参数可以不按照参数列表的顺序来:
decorate("Hello", right = "]<<<")
从上面还可以知道,可以混用未命名参数和带名参数。
变长参数
def sum(args: Int*) = { var result = 0 for( arg <- args) result += arg result }
星号表示可接受变长参数列表。函数实际得到的参数是Seq类型的。
但是在调用变长参数函数时,并不能直接使用Seq类型的参数。要将一个Seq类型打散成单个的参数序列,使用 : _*。
def recursiveSum(args: Int*) = { if (args.length == 0) 0 else args.head + recursiveSum(args.tail: _*) }
过程(procedure)
过程的定义与函数很类似。函数体在花括号中,而且花括号前没有=号,返回值是Unit类型的,这就是过程了。(也可以写出=号,并且写明返回值是Unit类型来定义过程。)
通常,过程没有返回值,只是为了副作用而调用一个过程。
懒值
当val被声明为lazy时,初始化会被推迟到首次对其取值时。
lazy val words = io.Source.fromFile("/usr/share/dict/words").mkString
如果程序一直不妨问words,那么Scala也不会去打开那个文件。文件是在实际第一次使用words时被打开的。
懒值对开销较大的初始化语句十分有用,可以应对一些初始化问题比如循环依赖。还是开发懒数据结构的基础。
可以将懒值当成是介于val和def的中间状态:
- val在被定义时即被取值
- lazy val在首次使用时被取值
- def在每次使用时取值
异常
Scala中异常机制与Java和C++中一样。但Scala中并没有受检异常,也就是不需要说明函数方法可能会抛出什么异常。
使用throw来抛出一个异常。throw表达式的类型是Nothing。
捕获异常:
try { process(new URL("http://horstmann.com/fred-tiny.gif")) } catch { case _: => MalformedURLException => println("Bad URL: " + url) case ex: IOException => ex.printStackTrace() }
如果不需要使用捕获的异常对象,使用_来代替变量名(_是Scala中的通配符)。
同时也有try/finally语句,通常用来清理。可以组合成try/catch/finally语句
fRom: http://nerd-is.in/2013-08/scala-learning-control-structures-and-functions/