语法基础

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

// 实际四则运算是调用对象的方法,比如把表达式按照方法调用来执行:
1.+(2) // => res11: Int = 3
(1).+(1) // => res13: Int = 2

// 让我们来看一个更灵活的例子:
List(1, 2, 3).map(_.+(10)) // => res20: List[Int] = List(11, 12, 13)

4.2 逻辑运算

2 > 2  // 大于
1 < 2  // 小于
2 >= 2 // 大于等于
2 <= 2 // 小于等于
2 != 1 // 不等于
/* 相等判断,和 Java 中“==”不一样,Java 中判断的是两个对象是否引用的同一个对象
 判断对象是否相等,如果两边不为 null,会调用对象的 equals 方法。*/
a == b

a && b // 逻辑与
a || b // 逻辑或
!a     // 逻辑非

// 位运算:
a & b   // 与
a | b   // 或
a ^ b   // 异或
~a      // 一元运算
a << 1  // 左移
a >> 1  // 右移
a >>> 4 // 无符号右移

5 字面量

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

Byte
Short
Int
Long
Char
String
Float
Dobule
Boolean
Unit(等同于Java中的void类型)
// 虽然 Scala 是静态类型的语言,但只要能推断类型,Scala 就会自动推断类型,比如:
// 这里不用为变量 a 声明类型,因为 Scala 可以根据值来推断出类型。
var a = 1

/* 整型 */
0x0a // 十六进制
10L  // 长整型
// 注意,Scala 2.10 不赞成使用 012 这样的八进制表达方式了,2.11 里已经被移除了,一些较旧的书中所说的八进制用“0”开头将报错:
012 // error: Decimal integer literals may not have a leading zero. (Octal syntax is obsolete.)

/* 字符,用单引号包围 */
'a'        // => a
'\101'     // => A,八进制
'\u0041'   // Unicode编码
'\\'       // => “\”需要转义

/* 字符串用双引号包围 */
"hello"

println("""hello // 3 个双引号可以忽略掉转移字符来表达 raw string。
       | world""")
// => hello // 3 个双引号可以忽略掉转移字符来表达 raw string。
//        | 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}")   // 还可以调用函数

/* 符号 */
// 类似 Lisp 的符号,写成:'xx。
// 和 Lisp 中用法也一样,一个符号指向同一内存地址,创建一个符号又叫作 intern,和 Lisp 中的概念一样。示例:
val A = 'a // => A: Symbol = 'a
// 之所以相等是因为 'a 都指向同一内存地址,做比较时只用比较是否是同一地址即可。如果是字符串比较的话,还需要逐一对比,所以符号的效率更高,但也要注意符号是不会被垃圾回收器给回收掉的(因为符号存在 JVM 的“常量池”中),所以不适合大量创建。
'a == A    // => res12: Boolean = true
'a eq A    // res13: Boolean = true

// 布尔类型用 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 对象

// 创建一个从 1 到 3 的 Range
1 to 3 // => res28: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3)
// 创建一个从 1 到 4 的 Range
1 until 5 // => 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