操作符重载?
- 举个例子[P60]
-
1 + 2
中的+
操作符是一个方法。
-
- 首先,在 Scala 中,万物皆对象,包括基本数据类型。其次,使用中缀表达式法表示单参数方法时,其中的点号和括号可以省略,所以:
1 + 2
等价于1.+(2)
。[P60] - 调用无参方法也可以省略点号,这种写法也被称为后缀表示法。由于有的时候后缀表示法会触发歧义,所以 Scala 2.10+ 将其作为可选特性,需要
import scala.language.postfixOps
,或者使用scala -language:postfixOps
开启 REPL 来开启这个特性。[P60 - 61] - 使用
scala -feature
开启 REPL 可以获取更多有意义的警告信息。[P61] - 允许出现在标示符中的字符:[P61 - 62]
[略]- Scala 没有自增 / 自减运算符。
- 在模式匹配表达式中,以小写字母开头的标记会被解析为变量标示符,而大写字母开头的标记则会被解析为常量标示符(如类名)。
无参数方法
-
Scala 允许用户决定是否为无参数方法使用括号:[P63]
- 在定义无参数方法时如果省略括号,那么调用该方法的时候必须省略括号。但是如果定义时没有省略括号,用户在调用时可以选择省略或不省略。
-
Scala 社区的习惯:[P63]
- 定义无副作用的无参方法时省略括号,有副作用的则添加括号。
在
scala
或者scalac
中添加-Xlint
参数时,定义有副作用的无参方法,省略括号时系统会发出警告。-
一个经典的省略括号的例子(方法链):[P63 - 64]
def isEven(n: Int) = (n % 2) == 0 List(1, 2, 3, 4) filter isEven foreach println // 展开(提示:关注一下每一个方法需要的参数,这里边并没有省略任何变量参数) List(1, 2, 3, 4).filter((i: Int) => isEven(i)).foreach((i: Int) => println(i)) List(1, 2, 3, 4).filter(i => isEven(i)).foreach((i: Int) => println(i)) List(1, 2, 3, 4).filter(isEven).foreach(println) List(1, 2, 3, 4) filter isEven foreach println
简单梳理一下:
filter
接收一个函数参数isEven
,并返回一个新的List
,然后调用foreach
,它也接收一个函数参数,这个函数正好是println
。其实参数一点都没省略,就是单纯的省略括号和省略.
运算符。假如方法链中的某一个方法接收 0 个或大于 1 个的参数,需要为部分或全部方法补上点号。
优先级规则
-
优先级表 [P64]
- 所有字母
- |
- ^
- &
- < >
- = !
- :
-
-
-
- / %
- 其他特殊字符
当 = 用于赋值时,优先级最低。[P64]
在 Scala 中,任何名字以
:
结尾的方法都与右边的对象所绑定,其他的方法则是左绑定的。[P65]-
举个例子:cons 操作:使用
::
方法将某一元素放置到列表前边。[P65]val list = List('b', 'c', 'd') val nList = 'a'::list
领域特定语言
[P65 - 66][略]
Scala 的 if
语句
Scala 的
if
语句是具有返回值的表达式。[P66]-
if
语句的返回值类型也被称为所有条件分支的最小上界,也就是与每条each
子句可能返回值类型最接近的父类型。以下例子中返回String
:[P66]val configFile = new java.io.File("somefile.txt") val configFilePath = if (configFile.exists()) { configFile.getAbsolutePath() // String } else { configFile.createNewFile() configFile.getAbsolutePath() // String }
Scala 没有三元表达式。[P66]
Scala 中的 for
推导式
生成器表达式
-
<-
[�P67]
保护式:筛选元素
-
通过加入
if
表达式来筛选元素,这些表达式也被称为保护式。[P68]val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier") for (breed <- dogBreeds if breed.contains("Terrier") ) println(breed)
val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier") for (breed <- dogBreeds if breed.contains("Terrier") if !breed.startsWith("Yorkshire") ) println(breed) // 等价于 for (breed <- dogBreeds if breed.contains("Terrier") && !breed.startsWith("Yorkshire") ) println(breed)
Yielding
-
使用
yield
关键字在for
表达式中生成新的集合,使用大括号替代圆括号,以相似的方法把参数列表封装在大括号中:[P68]val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier") val filteredDogBreeds = for { breed <- dogBreeds if breed.contains("Terrier) && !breed.startsWith("Yorkshire") } yield breed
当
for
推导式仅包含单一表达式时使用圆括号,当其包含多个表达式时使用大括号。[P69]
扩展作用域与值定义
-
Scala 允许在
for
表达式中的最初部分定义值,并可以在后面的表达式中使用该值。[P69]val dogBreeds = List("Doberman", "yorkshire Terrier", "Dachshund", "Scottish Terrier") for { breed <- dogBreeds upcasedBreed = breed.toUpperCase() // 尽管 upcasedBreed 不可变,但并不需要使用 val 对其进行限定。 } println(upcasedBreed)
-
在
for
表达式中使用Option
:[P69 - 70]val dogBreeds = List(Some("Doberman"), None, Some("Yorkshire Terrier"), Some("Dachshund")) for { breedOption <- dogBreeds // 系统隐式的加上了 if breedOption != None breed <- dogBreeds upcaseBreed = breed.toUpperCase() } println(uppercaseBreed) for { SOme(breed) <- dogBreeds upcasedBreed = breed.toUpperCase() } println(upcasedBreed)
当遍历某一集合或其他像
Option
这样的容器并试图提取值时,应该使用箭头。当执行并不需要迭代的赋值操作时,应该使用等于号。[P70]
使用 try
、catch
和 final
语句
Scala 不支持已被视为失败设计的检查型异常( checked exception )。Scala 将 Java 中检查异型异常视为非检查型,而且方法声明中也不包含
throw
子句。[P72]Scala 提供了有助于 Java 互操作的
@throws
注解。[P72]��-
在 Scala 中,异常处理用模式匹配来处理:[P72 - 73]
var source: Option[Source] = None try { source = Some(Source.fromFile(fileName)) val size = source.get.getLines.size println(s"file $fileName has $size lines") } catch { case NonFatal(ex) => println(s"Non fatal exception! $ex)" // 捕获 *非致命* 错误。 } finally { for (s <- source) { println(s"Closing $fileName...") s.close } }
在 SBT 中,使用
run-main
启动程序。启动程序前允许传递参数。[P74]Scala 允许自定义异常:
throw new MyBadException(...)
。如果自定义异常是一个 case 类,那么抛出异常时可以省略new
关键字。[P74]自动资源管理:ScalaARM
名字调用和值调用 [P75 - 78]
-
manage.apply
方法def apply[ R <: { def close():Unit }, // 1 T] (resource: => R) // 2 (f: R => T) = {...} // 3
-
<:
表明R
属于其他类的子类。 在本例中,R
的父类型是一个包含close():Unit
方法的结构类型。- 结构化类型允许我们使用反射机制嵌入包含
close():Unit
方法的任意类型,但会造成许多系统开销,结构化类型也的代价也十分昂贵,所以 Scala 将反射列为可选特性。
- 结构化类型允许我们使用反射机制嵌入包含
- 传名参数:在进入
manage.apply
之前,该表达式都不会被执行,直到在apply
中调用这个参数。(延迟计算) - 匿名函数,输入为
resource
返回值类型为T
。
-
传名参数的行为与函数类似,每次使用该参数时便会执行表达式。[P77]
-
运用传名参数,以递归取代循环结构:[P77]
@annotation.tailrec def continue(conditional: => Boolean)(body: => Unit) { if (conditional) { body continue(conditional)(body) } } var count = 0 continue(count < 5) { println(s"at $count") count += 1 }
传名参数会在每次被引用时估值。它的求值会被推迟,并可能一再地被重复调用,因此此类参数具有惰性。除此之外,Scala 还提供了惰性值。[P78]
惰性赋值
-
以延迟的方式初始化某个值,并且表达式不会被重复计算。常见场景:[P78]
- 表达式执行代价昂贵。
- 缩短模块的启动时间,将不需要的工作推迟。
- 确保对象中的其他字段的初始化过程优先执行。
-
举个例子:[P78]
object ExpensiveResource { lazy val resource: Int = init() // 推迟到需要时计算。 def init(): Int = { // Some Expensive Operations. 0 } {
Scala 通过保护式( guard )来实现惰性值,由于无法解除保护式,所以会造成额外的开销。只有当保护式带来的额外开销小于初始化带来的开销,或者某些值惰性化能简化系统初始化过程,并确保执行顺序满足依赖条件时才应该使用惰性值。[P78]
枚举
Scala
定义了Enumration
类来支持枚举,意味着 Scala 不为枚举提供特殊语法。[P79]-
一个例子:[P79 - 80]
object Breed extends Enumeration { type Breed = Value val doberman = Value("Doberman Pinscher") val yorkie = Value("Yorkshire Terrier") } // 在其他类中 for(breed <- Breed.values) println(s"${breed.id}, $breed")
使用
.values
取得枚举列表。Value
对象会自动从 0 开始逐一递增分配 ID 。-
在上述定义中,
Breed
是Value
类型的一个别名,目的在于:def isTerrier(b: Breed) = b.toString.endsWith("Terrier") Breed.values filter isTerrier foreach println
如果没有类型别名,上边的代码会出现语法错误。
-
有时不希望给枚举值命名:[P80]
object WeekDay extends Enumeration { type WeekDay = Value val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value } def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun) WeekDay.values filter isWorkingDay foreach println
case 类相比枚举值有两大有点:允许添加方法和字段;适用于包含未知枚举值的场景。[P81]
可插入字符串
- 在使用
printf
格式字符串时(不是printf
函数),Scala 会调用 Java 的Formatter
类。printf
格式指令与${...}
之间不能有空格。[P81] - 在格式字符串中,将
Double
用Int
格式化输出会引发编译错误。[P82] - Scala 编译器会在某些语境中对 Java 字符串进行封装并提供一些额外的方法,这些定义在
scala.collection.immutable.StringLike
中。[P82] - “原生”( raw )插入器不会对控制字符串进行扩展。[P82]
- Scala 允许自定义字符串插入器。[P83]
Trait
:Scala 语言的接口和“混入”
Scala 使用
trait
来替代接口。(一种简单的理解是:允许将声明方法实现的接口)[P83]trait
支持“混入”,即不只是定义方法和字段。可以参考书上关于日志系统的代码。[P84]使用
with
关键字混入trait
。Scala 允许混入多个trait
,混入的trait
中的方法彼此独立。[P84 - 85]-
子类向父类传参的方式:
class LoggedServiceImportante(name: String) extends ServiceImportante(name) with StdoutLogging {...} // 子类将 name 传递给父类
Scala 要求所有方法覆写都要使用
override
关键字。与 Java 一样,Scala 使用super.methodName
访问父类方法。