@@ -13,29 +13,29 @@ Lightsing 译
13
13
14
14
## 介绍
15
15
16
- 此教学将对 Scala 语言以及编译器做一个简易介绍。面向的读者为具有变成经验且想简要了解 Scala 的人。本文假设读者有着基本、特别是 Java 上的面向对象知识。
16
+ 此教学将对 Scala 语言以及编译器做一个简易介绍。面向的读者为具有编程经验,并且想简单了解 Scala 的人。本文假设读者有着基本的、最好是 Java 上的面向对象知识。
17
17
18
18
## 第一个例子
19
19
20
- 这里用标准的 * Hello world* 程序作为第一个例子。虽然它很无趣,可是这让我们在仅用少量语言特性下演示 Scala 工具。程序如下:
20
+ 这里用标准的 * Hello world* 程序作为第一个例子。虽然它很无趣,但让我们可以用少量语言特质来演示 Scala 工具。程序如下:
21
21
22
22
object HelloWorld {
23
23
def main(args: Array[String]) {
24
24
println("Hello, world!")
25
25
}
26
26
}
27
27
28
- Java 程序员应该对这个程序结构感到熟悉:这有一个` main ` 函数,该函数接受一个字符串阵列参数,也就是命令列参数 ;函数内容为调用已定义好的函数` println ` 并用Hello world 字符串当参数。 ` main ` 函数没有回传值 (它是程序函数)。因此并不需要声明回传类型 。
28
+ Java 程序员应该对这个程序结构感到熟悉:这有一个` main ` 函数,该函数接受一个字符串数组作为参数,即命令行参数 ;函数内容为调用已定义好的函数` println ` 并用Hello world 字符串当参数。 ` main ` 函数没有返回值 (它是一个过程方法)。因此并不需要声明返回值类型 。
29
29
30
- Java 程序员不太熟悉的是包着 ` main ` 函数的 ` object ` 声明。这种声明引入我们一般称之 * Singleton* 的东西,也就是只有一个实例的类。所以上面的声明同时声明了一个 ` HelloWorld ` 类跟一个这类的实例 ,也叫做 ` HelloWorld ` 。该实例会在第一次被使用到的时候即时产生。
30
+ Java 程序员不太熟悉的是包着 ` main ` 函数的 ` object ` 声明。这种声明引入我们一般称之 * Singleton* 的东西,也就是只有一个实例的类。所以上面的代码同时声明了一个 ` HelloWorld ` 类和一个这类的实例 ,也叫做 ` HelloWorld ` 。该实例会在第一次被使用到的时候即时产生。
31
31
32
32
眼尖的读者可能已经注意到这边 ` main ` 函数的声明没有带着 ` static ` 。这是因为 Scala 没有静态成员 (函数或属性)。 Scala 程序员将这成员声明在单实例对象中,而不是定义静态成员。
33
33
34
- ### 编译这例子
34
+ ### 编译这个例子
35
35
36
36
我们用 Scala 编译器 ` scalac ` 来编译这个例子。 ` scalac ` 就像大多数编译器一样,它接受源代码文件当对象,并接受额外的选项,然后产生一个或多个对象文件。它产出的对象文件为标准 Java class 文件。
37
37
38
- 如果我们将上面的程序存成 ` HelloWorld.scala ` 档,编译指令为( ` > ` 是提示字符,不用打):
38
+ 如果我们将上面的程序存为文件 ` HelloWorld.scala ` 档,编译指令为( ` > ` 是提示字符,不用打):
39
39
40
40
> scalac HelloWorld.scala
41
41
@@ -51,7 +51,7 @@ Java 程序员不太熟悉的是包着 `main` 函数的 `object` 声明。这种
51
51
52
52
## 与 Java 互动
53
53
54
- Scala 的优点之一是它非常容易跟 Java 代码沟通。预设导入了所有 ` java.lang ` 底下之类,其他类则需要明确导入。
54
+ Scala 的优点之一是它非常容易跟 Java 代码沟通。Scala 会默认 import ` java.lang ` 底下之类,其他类则需要明确导入。
55
55
56
56
让我们看个展示这点的示例。取得当下日期并根据某个特定国家调整成该国格式,如法国。
57
57
@@ -73,15 +73,15 @@ Scala 的导入表达式跟 Java 非常像,但更为强大。如第一行,
73
73
74
74
所以第三行的表达式导入所有 ` DateFormat ` 类的成员。这让静态方法 ` getDateInstance ` 跟静态属性 ` LONG ` 可直接被使用。
75
75
76
- 在 ` main ` 函数中我们先创造一个 Java 的 ` Date ` 类实例,该实例预设拥有现在的日期 。接下来用 ` getDateInstance ` 函数定义日期格式。最后根据地区化的 ` DateFormat ` 实例对现在日期设定格式并印出。最后一行展现了一个 Scala 有趣特点。只需要一个对象的函数可以用中缀语法调用。就是说,这个表达式
76
+ 在 ` main ` 函数中我们先创造一个 Java 的 ` Date ` 类实例,该实例默认拥有现在的日期 。接下来用 ` getDateInstance ` 函数定义日期格式。最后根据地区化的 ` DateFormat ` 实例对现在日期设定格式并印出。最后一行展现了一个 Scala 有趣特点。只需要一个对象的函数可以用中缀语法调用。就是说,这个表达式
77
77
78
78
df format now
79
79
80
80
是这个表达式的简略版本
81
81
82
82
df.format(now)
83
83
84
- 这点也许看起来只是语法上的小细节,但是它有着重要的后果,其中一个将会在下一节做介绍 。
84
+ 它看起来也许只是语法上的小细节,但却有着重要的影响,其中一个影响将会在下一节做介绍 。
85
85
86
86
最后值得一提的是,Scala 可以直接继承 Java 类或者实现 Java 接口。
87
87
@@ -115,9 +115,9 @@ Scala 是一个纯粹的面向对象语言,这句话的意思是说,*所有
115
115
116
116
可能令 Java 程序员更为惊讶的会是,Scala 中函数也是对象。因此,将函数当做对象传递、把它们存入变量、从其他函数返回函数都是可能的。能够像操作变量一样的操作函数这点是* 函数式编程* 这一非常有趣的程序设计思想的基石之一。
117
117
118
- 为何把函数当做变量一样的操作会很有用呢,让我们考虑一个定时函数,功能是每秒执行一些动作。我们要怎么将这动作传给它?最直接的便是将这动作视为函数传入。应该有不少程序员对这种简单传递函数的行为很熟悉:通常在使用者介面相关的程序上,用以注册一些当事件发生时被调用的回呼函数 。
118
+ 为何把函数当做变量一样的操作会很有用呢,让我们考虑一个定时函数,功能是每秒执行一些动作。我们要怎么将这动作传给它?最直接的便是将这动作视为函数传入。应该有不少程序员对这种简单传递函数的行为很熟悉:通常在用户界面相关的程序上,用来注册一些当事件发生时被调用的回调函数 。
119
119
120
- 在接下来的程序中,定时函数叫做 ` oncePerSecond ` ,它接受一个回呼函数做参数 。该函数的类型被写作 ` () => Unit ` ,这个类型便是所有无对象且无返回值函数的类型( ` Unit ` 这个类型就像是 C/C++ 的 ` void ` )。此程序的主函数只是调用定时函数并带入回呼函数,回呼函数输出一句话到终端上。也就是说这个程序会不断的每秒输出一次 "time flies like an arrow"。
120
+ 在接下来的程序中,定时函数叫做 ` oncePerSecond ` ,它接受一个回调函数做参数 。该函数的类型被写作 ` () => Unit ` ,这个类型便是所有无对象且无返回值函数的类型( ` Unit ` 这个类型就像是 C/C++ 的 ` void ` )。此程序的主函数只是调用定时函数并带入回呼函数,回呼函数输出一句话到终端上。也就是说这个程序会不断的每秒输出一次 "time flies like an arrow"。
121
121
122
122
object Timer {
123
123
def oncePerSecond(callback: () => Unit) {
@@ -131,7 +131,7 @@ Scala 是一个纯粹的面向对象语言,这句话的意思是说,*所有
131
131
}
132
132
}
133
133
134
- 值得注意的是,这边输出时我们使用 Scala 的函数 ` println ` ,而不是 ` System.out ` 里的函数 。
134
+ 值得注意的是,在打印字符串时,我们使用的是 Scala 预定义的方法 ` println ` ,而不是 ` System.out ` 中的 。
135
135
136
136
#### 匿名函数
137
137
@@ -151,7 +151,7 @@ Scala 是一个纯粹的面向对象语言,这句话的意思是说,*所有
151
151
152
152
## 类
153
153
154
- 之前已讲过,Scala 是一个面向对象语言,因此它有着类的概念 (更精确的说,的确有一些面向对象语言没有类的概念,但是 Scala 不是这种) 。Scala 声明类的语法跟 Java 很接近。一个重要的差别是,Scala 的类可以有参数。这边用底下复数的定义来展示 :
154
+ 之前已讲过,Scala 是一个面向对象语言,因此它有着类的概念 (更精确的说,的确有一些面向对象语言没有类的概念,但是 Scala 不是其中之一) 。Scala 声明类的语法跟 Java 很接近。一个重要的差别是,Scala 的类可以有参数。如下面展示的复数的定义 :
155
155
156
156
class Complex(real: Double, imaginary: Double) {
157
157
def re() = real
@@ -162,7 +162,7 @@ Scala 是一个纯粹的面向对象语言,这句话的意思是说,*所有
162
162
163
163
值得注意的是,这两个函数的回传值并没有被明确给定。编译器将会自动的推断,它会查看这些函数的右侧并推导出这两个函数都会回传类型为 ` Double ` 的值。
164
164
165
- 编译器并不一定每次都能够推断出类型,而且很不幸的是我们并没有简单规则以分辨哪种情况能推断,哪种情况不能。因为当编译器无法推断未明确给定的类型时它会回报错误,实务上这通常不是问题 。Scala 初学者在遇到那些看起来很简单就能推导出类型的情况时,应该尝试着忽略类型声明并看看编译器是不是也觉得可以推断。多尝试几次之后程序员应该能够体会到何时忽略类型、何时该明确指定。
165
+ 编译器并不一定每次都能够推断出类型,而且很不幸的是我们并没有简单规则以分辨哪种情况能推断,哪种情况不能。实践上这通常不是问题,因为当编译器无法推断未明确给定的类型时,它会报错 。Scala 初学者在遇到那些看起来很简单就能推导出类型的情况时,应该尝试着忽略类型声明并看看编译器是不是也觉得可以推断。多尝试几次之后程序员应该能够体会到何时忽略类型、何时该明确指定。
166
166
167
167
### 无对象函数
168
168
@@ -248,11 +248,11 @@ Java 中我们会将这个树用一个抽象父类表示,然后每种节点跟
248
248
3 . 如果第二个检查也失败,表示树不是 ` Sum ` 也不是 ` Var ` ,那便检查是不是 ` Const ` ,如果是的话将 ` Const ` 所带的名称绑定到变量 ` v ` 并求值右侧表达式,
249
249
4 . 最后,如果全部检查都失败,会抛出异常表示匹配失败;这只会在有更多 ` Tree ` 的子类时发生。
250
250
251
- 如上,模式匹配基本上就是尝试将一个值对一系列模式做匹配,并在一个模式成功匹配时抽取并命名该值的各部分,最后对一些代码求值,而这些代码通常会利用被命名到的部位 。
251
+ 如上,模式匹配基本上就是尝试将一个值对一系列模式做匹配,并在一个模式成功匹配时抽取并命名该值的各部分,最后对一些代码求值,而这些代码通常会利用被命名到的部分 。
252
252
253
253
一个经验丰富的面向对象程序员也许会疑惑为何我们不将 ` eval ` 定义成 ` Tree ` 类跟子类的* 方法* 。由于 Scala 允许在 case class 中跟一般类一样定义函数,事实上我们可以这样做。要用模式匹配或是函数只是品味的问题,但是这会对扩充性有重要影响。
254
254
255
- - 当使用函数时,只要定义新的 ` Tree ` 子类便新增新节点,相当容易。另一方面,增加新操作需要修改所有子类,很麻烦 。
255
+ - 当使用函数时,增加新的节点类型是相当容易的, 只要定义新的 ` Tree ` 子类即可。不过另一方面,为树增加新操作很麻烦,因为它需要修改 ` Tree ` 的所有子类 。
256
256
- 当使用模式匹配时情况则反过来:增加新节点需要修改所有对树做模式匹配的函数将新节点纳入考虑;增加新操作则很简单,定义新函数就好。
257
257
258
258
让我们定义新操作以更进一步的探讨模式匹配:对符号求导数。读者们可能还记得这个操作的规则:
@@ -291,17 +291,17 @@ Java 中我们会将这个树用一个抽象父类表示,然后每种节点跟
291
291
Derivative relative to y:
292
292
Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1)))
293
293
294
- 研究这输出我们可以发现,取导数的结果应该在输出前更进一步化简。用模式匹配实作一个基本化简函数是一个很有趣 (但是意外的棘手) 的问题,在这边留给读者当练习。
294
+ 研究这输出我们可以发现,取导数的结果应该在输出前更进一步化简。用用模式匹配实现一个基本化简函数是一个很有趣 (但是意外的棘手) 的问题,在这边留给读者当练习。
295
295
296
- ## 特性 (Traits)
296
+ ## 特质 (Traits)
297
297
298
- 除了由父类继承行为以外,Scala 类还可以从一或多个* 特性 * 导入行为。
298
+ 除了由父类继承行为以外,Scala 类还可以从一或多个* 特质 * 导入行为。
299
299
300
- 对一个 Java 程序员最简单去理解特性的方式应该是视它们为带有实例的接口 。在 Scala 里,当一个类继承特性时,它实作了该特性的介面并继承所有特性带有的功能 。
300
+ 对一个 Java 程序员最简单去理解特质的方式应该是视它们为带有实例的接口 。在 Scala 里,当一个类继承特质时,它实现了该特质的接口并继承所有特质带有的功能 。
301
301
302
- 为了理解特性的用处 ,让我们看一个经典示例:有序对象。大部分情况下,一个类所产生出来的对象之间可以互相比较大小是很有用的,如排序它们。在Java里可比较大小的对象实作 ` Comparable ` 介面。在Scala中借由定义等价于 ` Comparable ` 的特性 ` Ord ` ,我们可以做的比Java稍微好一点。
302
+ 为了理解特质的用处 ,让我们看一个经典示例:有序对象。大部分情况下,一个类所产生出来的对象之间可以互相比较大小是很有用的,如排序它们。在Java里可比较大小的对象实作 ` Comparable ` 介面。在Scala中借由定义等价于 ` Comparable ` 的特质 ` Ord ` ,我们可以做的比Java稍微好一点。
303
303
304
- 当在比较对象的大小时,有六个有用且不同的谓词 (predicate):小于、小于等于、等于、不等于、大于等于、大于。但是把六个全部都实现很烦,尤其是当其中有四个可以用剩下两个表示的时候。也就是说,(举例来说) 只要有等于跟小于谓词,我们就可以表示其他四个。在 Scala 中这些观察可以很漂亮的用下面的特性声明呈现 :
304
+ 当在比较对象的大小时,有六个有用且不同的谓词 (predicate):小于、小于等于、等于、不等于、大于等于、大于。但是把六个全部都实现很烦,尤其是当其中有四个可以用剩下两个表示的时候。也就是说,(举例来说) 只要有等于跟小于谓词,我们就可以表示其他四个。在 Scala 中这些观察可以很漂亮的用下面的特质声明呈现 :
305
305
306
306
trait Ord {
307
307
def < (that: Any): Boolean
@@ -322,7 +322,7 @@ Java 中我们会将这个树用一个抽象父类表示,然后每种节点跟
322
322
def day = d
323
323
override def toString(): String = year + "-" + month + "-" + day
324
324
325
- 这边要注意的是声明在类名称跟参数之后的 ` extends Ord ` 。这个语法声明了 ` Date ` 继承 ` Ord ` 特性 。
325
+ 这边要注意的是声明在类名称跟参数之后的 ` extends Ord ` 。这个语法声明了 ` Date ` 继承 ` Ord ` 特质 。
326
326
327
327
然后我们重新定义继承自 ` Object ` 的 ` equals ` 函数好让这个类可以正确的根据每个属性来比较日期。因为在 Java 中 ` equals ` 直接比较实际对象本身,并不能在这边用。于是我们有下面的例子:
328
328
@@ -346,19 +346,19 @@ Java 中我们会将这个树用一个抽象父类表示,然后每种节点跟
346
346
(month == o.month && day < o.day)))
347
347
}
348
348
349
- 这边使用了另外一个预定义函数 ` error ` ,它会丢出带着给定错误信息的例外。这便完成了 ` Date ` 类。这个类的实例可被视为日期或是可比较对象。而且它们通通都定义了之前所提到的六个比较谓词: ` equals ` 跟 ` < ` 直接出现在类定义当中,其他则是继承自 ` Ord ` 特性 。
349
+ 这边使用了另外一个预定义函数 ` error ` ,它会丢出带着给定错误信息的例外。这便完成了 ` Date ` 类。这个类的实例可被视为日期或是可比较对象。而且它们通通都定义了之前所提到的六个比较谓词: ` equals ` 跟 ` < ` 直接出现在类定义当中,其他则是继承自 ` Ord ` 特质 。
350
350
351
- 特性在其他场合也有用 ,不过详细探讨它们的用途并不在本文件目标内。
351
+ 特质在其他场合也有用 ,不过详细探讨它们的用途并不在本文件目标内。
352
352
353
353
## 泛型
354
354
355
- 在这份教学里,我们最后要探讨的 Scala 特性是泛型 。Java 程序员应该相当清楚在 Java 1.5 之前缺乏泛型所导致的问题。
355
+ 在这份教学里,我们最后要探讨的 Scala 特质是泛型 。Java 程序员应该相当清楚在 Java 1.5 之前缺乏泛型所导致的问题。
356
356
357
357
泛型指的是能够将类型也作为程序参数。举例来说,当程序员在为链表写函数库时,它必须决定列表的元素类型为何。由于这列表是要在许多不同场合使用,不可能决定列表的元素类型为如 ` Int ` 一类。这样限制太多。
358
358
359
359
Java 程序员采用所有对象的父类 ` Object ` 。这个解决办法并不理想,一方面这并不能用在基础类型 (` int ` 、` long ` 、` float ` 之类),再来这表示必须靠程序员手动加入大量的动态转型。
360
360
361
- Scala 借由可定义泛型类 (跟函数) 来解决这问题。让我们借由最简单的类容器来检视这点:参照 ,它可以是空的或者指向某类型的对象。
361
+ Scala 借由可定义泛型类 (跟函数) 来解决这问题。让我们借由最简单的类容器来观察这点:引用 ,它可以是空的或者指向某类型的对象。
362
362
363
363
class Reference[T] {
364
364
private var contents: T = _
0 commit comments