读书笔记——

Scala for the Impatient

作者:[美] Cay S. Horstmann 著 (高宇翔 译)

第1章 基础

1.1 Scala解释器

启动Scala解释器的步骤如下:

  • 安装Scala
  • 确保scala/bin目录位于系统PATH中
  • 在你的操作系统中打开命令行窗口
  • 键入scala并按Enter键

示例:

scala> 8 * 5 + 2
res0: Int = 42
scala> 0.5 * res0
res1: Double = 21.0
scala> "Hello," + res0
res2: java.lang.String = Hello, 42

从技术上讲,scala程序并不是一个解释器。实际发生的是,你输入的内容被快速地编译成字节码,然后这段字节码交由Java虚拟机执行。正因如此,大多数Scala程序员更倾向于将它称做”REPL”。

1.2 声明值和变量

scala> val answer = 8 * 5 + 2
answer: Int = 42
scala> 0.5 * answer
res3: Double = 21.0

以val定义的值实际上是一个常量——你无法改变它的内容。

如果要声明其值可变的变量,可以用var。

在Scala中,我们鼓励你使用val——除非你真的需要改变它的内容。

在必要的时候,可以指定变量类型:

val greeting: String = null

在Scala中,仅当同一行代码中存在多条语句时才需要用分号隔开。

你可以将多个值或变量放在一起声明:

val xmax, ymax = 100
val greeting, message: String = null

1.3 常用类型

Scala的类型都是类,Scala并不刻意区分基本类型和引用类型。你可以对数字执行方法,例如:

1.toString()    // 产出字符串"1"
1.to(10)        // 产出Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

在Scala中,我们不需要包装类型。在基本类型和包装类型之间的转换是Scala编译器的工作。举例来说,如果你创建一个Int的数组,最终在虚拟机中得到的是一个int[]数组。

Scala用底层的java.lang.String类来表示字符串。不过,它通过StringOps类给字符串追加了上百种操作。

举例来说,intersect方法输出两个字符串共通的一组字符:

"Hello".intersect("World")

在这个表达式中,java.lang.String对象”Hello”被隐式地转换成了一个StringOps对象,接着StringOps类的intersect方法被应用。

同样地,Scala还提供了RichInt、RichDouble、RichChar等。它们分别提供了它们可怜的堂兄弟们——Int、Double、Char等所不具备的便捷方法。我们前面用到的to方法事实上就是RichInt类中的方法。在表达式:

1.to(10)

中,Int值1首先被转换成RichInt,然后再应用to方法。

最后,还有BigInt和BigDecimal类,用于任意大小(但有穷)的数字。这些类背后分别对应的是java.math.BigInteger和java.math.BigDecimal,不过,它们用起来更加方便,你可以用常规的数学操作符来操作它们。

1.4 算术和操作符重载

Scala的操作符实际上是方法,如:

a + b

是如下方法调用的简写:

a.+(b)

通常来说,你可以用:

a 方法 b

作为以下代码的简写:

a.方法(b)

Scala并没有提供++和–操作符,我们需要使用+=1或者-=1:

counter += 1

对于Bigint和BigDecimal对象,你可以以常规的方式使用那些数学操作符:

val x: BigInt = 1234567890
x * x * x 

1.5 调用函数和方法

相比Java,在Scala中使用数学函数(比如min或pow)更为简单——你不需要从某个类调用它的静态方法。

sqrt(2)
pow(2, 4)
min(3, Pi)

这些数学函数是在scala.math包中定义的。你可以用如下语句进行引入:

import scala.math._    // 在Scala中,_字符是“通配符”,类似Java中的*

使用以scala.开头的包时,我们可以省去scala的前缀。例如,import math.等同于import scala.math.,而math.sqrt(2)等同于scala.math.sqrt(2)。

Scala没有静态方法,不过它有个类似的特性,叫做单例对象(singleton object)。通常,一个类对应有一个伴生对象(companion object)。举例来说,BigInt类的BigInt伴生对象有一个生成指定位数的随机素数的方法probablePrime:

BigInt.probablePrime(100, scala.util.Random)

这里的Random是一个单例的随机数生成器对象。这是用单例对象比用类更好的为数不多的场景之一。在Java中,为每个随机数都构造出一个新的java.util.Random对象是一个常见的错误。

不带参数的scala方法通常不使用圆括号:

"Hello".distinct

1.6 apply方法

"Hello"(4)

是如下语句的缩写:

"Hello".apply(4)

又如:

BigInt("1234567890")

是如下语句的缩写:

BigInt.apply("1234567890")

这个语句产出一个新的BigInt对象,不需要使用new。

像这样使用伴生对象的Apply方法是Scala中构建对象的常用方法。例如,Array(1, 4, 9, 16)返回一个数组,用的就是Array伴生对象的apply方法。

1.7 Scaladoc

以下这些小窍门:

  • 如果你想使用数值类型,记得看看RichInt、RichDouble等。同理,如果想使用字符串,记得看看StringOps。
  • 那些数学函数位于scala.math包中,而不是位于某个类中。
  • 有时你会看到名称比较奇怪的函数。比如BigInt有一个方法叫做unary_-,这就是你定义前置的负操作符-x的方式。
  • 标记为implicit的方法对应的是自动(隐士)转换。
  • 方法可以以函数作为参数。
  • 你时不时地会遇到类似Range或Seq[Char]这样的类。它们的含义和你的直觉告诉你的一样:一个是数字区间,一个是字符序列。

第2章 控制结构和函数

2.1 条件表达式

在Scala中if/else表达式有值,这个值就是跟在if或else之后的表达式的值。例如:

if (x > 0) 1 else -1

你可以将if/else表达式的值赋值给变量:

val s = if (x > 0) 1 else -1

在Scala中,每个表达式都有一个类型。以下是混合类型表达式:

if (x > 0) "positive" else -1

上述表达式的公共超类型叫做Any。

Scala引入一个Unit类,写做(),如:

if (x > 0) 1 else ()

你可以把()当做是表示“无有用值”的占位符,将Unit当做Java或C++中的void。

Scala没有switch语句,不过它有一个强大得多的模式匹配机制。

2.2 语句终止

2.3 块表达式和赋值

在Scala中,{}块包含一系列表达式,其结果也是一个表达式。块中最后一个表达式的值就是块的值。

在Scala中,赋值动作本身是没有值的——或者,更严格地说,它们的值是Unit类型的。

一个以赋值语句结束的块,比如:

{r = r * n; n -= 1}

的值是Unit类型的。

由于赋值语句的值是Unit类型的,别把它们串接在一起。

x = y = 1    // 别这样做

y = 1的值是(),你几乎不太可能想把一个Unit类型的值赋值给x。

2.4 输入和输出

print("Answer: ")
println(42)

与下面的代码输出的内容相同:

println("Answer: " + 42)

另外,还有一个带有C风格格式化字符串的printf函数:

printf("Hello, %s! You are %d years old. \n", "Fred", 42)

readLine函数从控制台读取一行输入。readLine带一个参数作为提示字符串:

val name = readLine("Your name: ")
print ("Your age: ")
val age = readInt()
printf("Hello, %s! Next year, your will be %d.\n", name, age + 1)

2.5 循环

while循环:

while (n > 0) {
	r = r * n
	n -= 1
}

Scala没有与for循环直接对应的结构:

for (i <- 1 to n)
	r = r * i

下标从0开始:

for (i <- 0 until s.length) 
	sum += s(i)

不需要使用下标:

var sum = 0
for (ch <- "Hello") sum += ch

Scala并没有提供break或continue语句来退出循环。那么如果需要break时怎么做?

  • 使用Boolean型的控制变量。
  • 使用嵌套函数——你可以从函数中return。
  • 使用Breaks对象中break方法。

如:

import scala.util.control.Breaks._
breakable {
	for (...) {
		if (...) break;    // 退出breakable块
		...
	}
}

在这里,控制权的转移是通过抛出和捕获异常完成的,因此,如果时间很重要的话,你应该尽量避免使用这套机制。