语法基础

Table of Contents

1 注释

多行注释:/* … */

单行注释://

2 分号和换行

“;”是行结束的可选符号,多行表达式不用分号结尾。分号通常用于多行表达式写到一行里:

var a = 1; println(a)

如果要换成多行的表达式在括号里,可以任意换行,比如:

val a = 1
val b = 2
println(
a
+
b
)

如果没有括号的表达式不可跨行,下例为错误代码:

val a = 1
val b = 2
a
+
b

3 变量定义

  • val 关键字定义变量值不可改变,类似 Java 里的 final 关键字;
  • var,可任意改变。

例:

val msg = "hello world"
val msg: String = "hello world"
val msg:java.lang.String = "hello world"

如果变量名和 Scala 的关键字重复,可以用一对“`”来转义。例如 Scala 有一个 yield 关键字,而 Thread 也有一个 yield 方法:

scala> Thread.yield // 直接调用会报错
<console>:1: error: identifier expected but 'yield' found.
       Thread.yield
	      ^

scala> Thread.`yield` // 转义下

4 基本运算

4.1 四则运算

1 + 1 // 2
2 - 1 // 1
1 * 1 // 1
10 / 3 // => 3

4.1.1 深入到四则运算背后

实际四则运算是调用对象的方法,比如把表达式按照方法调用来执行:

scala> 1.+(2)
res11: Int = 3

scala> (1).+(1)
res13: Int = 2

让我们来看一个更灵活的例子:

scala> List(1, 2, 3).map(_.+(10))
res20: List[Int] = List(11, 12, 13)

关于List和相应的map方法在后面的章节可看到。

4.2 逻辑运算

几种逻辑判断:

方法 说明 示例
> 大于 2 > 2
< 小于 1 < 2
>= 大于等于 2 >= 2
<= 小于等于 2 <= 2
!= 不等于 2 != 1

逻辑运算:

方法 说明
&& 逻辑与
|| 逻辑或
! 逻辑非

位运算:

方法 说明
&
|
^ 异或
~ 一元运算
<< 左移
>> 右移
>>> 无符号右移

4.3 相等判断

和 Java 中“==”不一样,Java 中判断的是两个对象是否引用的同一个对象。

==,判断对象是否相等,如果两边不为 null,会调用对象的 equals 方法。

5 字面量

字面量是固定值的表示。如字符串、整型等,Scala 是一门静态类型语言,大致有以下字面量:

Byte
Short
Int
Long
Char
String
Float
Dobule
Boolean
Unit(等同于Java中的void类型)

虽然 Scala 是静态类型的语言,但只要能推断类型,Scala 就会自动推断类型,比如:

var a = 1

这里不用为变量 a 声明类型,因为 Scala 可以根据值来推断出类型。

5.1 整型

十六进制:

val x = 0x0a
注意,Scala 2.10 不赞成使用 012 这样的八进制表达方式了,2.11 里已经被移除了,一些较旧的书中所说的八进制用“0”开头将报错:

scala> 012
<console>:1: error: Decimal integer literals may not have a leading zero. (Octal syntax is obsolete.)
       012
       ^

长整型:

val = 10L

5.2 字符

字符用单引号包围:

'a' // => a
'\101' // => A,八进制
'\u0041' // Unicode编码
'\\' // =>  \。需要转义

5.3 字符串

字符串用双引号包围:

scala> "hello"
res6: String = hello

scala> println("""hello // 3 个双引号可以忽略掉转移字符来表达 raw string。
     | world""")
hello
World

字符串插值(string interpolation):

val blog = "www.shellcodes.org"
println(s"My blog is: $blog")
println(s"My blog is: ${blog}") // 第二种表示法
println(s"My blog is: ${blog.toUpperCase}") // 还可以调用函数

5.4 符号

类似 Lisp 的符号,写成:'xx。

和 Lisp 中用法也一样,一个符号指向同一内存地址,创建一个符号又叫作 intern,和 Lisp 中的概念一样。示例:

scala> val A = 'a
A: Symbol = 'a
scala> 'a == A
res12: Boolean = true
scala> 'a eq A
res13: Boolean = true

之所以相等是因为 'a 都指向同一内存地址,做比较时只用比较是否是同一地址即可。如果是字符串比较的话,还需要逐一对比,所以符号的效率更高,但也要注意符号是不会被垃圾回收器给回收掉的(因为符号存在 JVM 的“常量池”中),所以不适合大量创建。

5.5 布尔

true 和 false

6 控制结构

6.1 if

if 表达式
else 表达式

或:

if 表达式
else if 表达式
else 表达式

示例:

println(if (1 > 0) "a" else "b")

6.2 while

示例:

var i = 0

while (i < 100) {
  i += 1
}

6.3 do-while

示例:

var i = 100

do {
  i -= 1
} while (i != 0)

6.4 for

迭代一个集合,如:

for ( i <- Array(1, 2, 3, 4))
  println(i + 10)

i <- Array(1, 2, 3, 4) 是一个生产者( generator)。

6.5 Range 对象

示例:

scala> 1 to 3 // 创建一个从1到3的 Range
res28: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3)

scala> 1 until 5 // 创建一个从1到4的 Range
res29: scala.collection.immutable.Range = Range(1, 2, 3, 4)

以用by指定步长:

0 to 10 by 2 // => (0, 2, 4, 6, 8, 10)

步长也可以是负数:

(10 to 0) by -2 // => Range(10, 8, 6, 4, 2, 0)

还可以按字符范围:

'a' to 'z' // => NumericRange(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z)

6.6 continue 和 break

注意 Scala 里默认没有 continue 和 break,也不提倡使用。可以尝试递归等方式来代替。

如果一定要使用 break,可以导入 scala.util.control.Breaks 里的 break,代码如下:

import scala.util.control.Breaks._

var i = 0

breakable {
  while (true) {
    if (i > 100) break else i += 1
  }
}

println(i)

这里的 break 函数会触发一个异常,然后 breakable 来捕获。

6.7 过滤

类似 Python 里的列表解析,如:

val files = (new java.io.File("/my/Books")).listFiles

for (
  file <- files
  if file.getName.endsWith(".pdf")) {
  println(file)
}

6.8 嵌套迭代

如果有多个 <- 出现在 for 里,会作为嵌套循环,如实现一个简单的 fgrep:

def getFileLines(file: java.io.File) = scala.io.Source.fromFile(file).getLines().toList

val files = (new java.io.File("/my/mycode")).listFiles

def fgrep(pattern: String) =
  for (
    file <- files
    if file.isFile; // 加个分号不会被解析成 else 部分
    line <- getFileLines(file)
    if line.indexOf(pattern) > -1
  ) println(file + ": " + line)

fgrep(args(0))

6.9 中间变量绑定(midstream variable binding)

可以在 for 循环中绑定变量,这个极像 Common Lisp 的 loop:

def getFileLines(file: java.io.File) = scala.io.Source.fromFile(file).getLines().toList

val files = (new java.io.File("/my/mycode")).listFiles

def grep(pattern: String) =
  for (
    file <- files
    if file.isFile;
    line <- getFileLines(file);  // 这里一定要有分号分隔
    find = line.indexOf(pattern)
    if find > -1
  ) println(file + ": " + line)

grep(args(0))

6.10 生成新集合

使用 for-yield 表达式可以生成一个可遍历的对象。如下代码,可以给每个文件名加一个“test_”前缀:

val files = (new java.io.File("/etc")).listFiles
for (
  file <- (for (file <- files) yield {"test_" + file})
) println(file)

6.11 定义本地变量

以下是在控制结构体中定义局部变量的方法:

var i = 0

while (i <= 10) {
  var j = 1
  i += 1
}

println(j) // not found: value j

这里 j 的作用域在 while 循环体中,所以最后行执行时会报错。同样这样也不行:

var i = 0

{
  val a = 2
}

println(a) // not found: value a