教材:快学Scala
markdown阅读:https://www.zybuluo.com/mdeditor
chapter 1. 基础
1.2 声明值(val)和变量(var)
val xmax, ymax = 100 // 将xmax和ymax设为100
var greeting, message: string = null // greeting和message都是字符串,被初始化为null
1.3 常用类型
-
Byte
Char
Short
Int
Long
Float
Double
Boolean
这些都是类 - String: 使用底层的
java.lang.String
表示字符串,但通过StringOps
类为字符串追加上百种操作
1.toString() // return "1"
1.to(10) // return Range(1,2,3,4,5,6,7,8,9,10)
"Hello".intersect("World") // 求交集 return "lo"
更多:
-
RichInt
RichDouble
RichChar
为Int Double Char 提供更多的方法(如前面的to()) -
BigInt
BigDecimal
用于任意大小的数字
1.4 算术和操作符重载
- 操作符+-*/都是方法 a+b 等价于
a.+(b)
-
a 方法 b
作为a.方法(b)
的简写 1.to(10) 可以写成 1 to 10 - 没有 ++ --(自增/自减) 有 += -=
1.5 调用函数和方法
import scala.math._ // _是Scala的通配符,类似Java的*
min(3, Pi)
pow(2, 4)
- Scala没有静态方法,类似特性叫单例对象(singleton object),对应有一个伴生对象(companion object)
- e.g. BigInt类的BigInt伴生对象有一个生成指定位数的随机素数方法probablePrime
BigInt.probablePrime(100, scala.util.Random)
-
不带参数且不改变当前对象的Scala方法通常不使用圆括号 e.g.
"hello".distinct
1.6 apply方法
- 如果s是一个字符串,s(i)代表s的第i个字符
"HELLO"(4) // return 'O'
- 原理:()操作符的重载形式,实现方式是一个名为apply的方法
-
"HELLO"(4)
等价于"HELLO".apply(4)
- BigInt的apply方法:
BigInt(2).pow(1023)
chapter 2. 控制结构和函数
- C++/Java: 表达式(3+4) 和语句(if) 是两种不同的东西,表达式有值,语句执行操作
- Scala: 几乎所有都是表达式(有值)
2.1 条件表达式(if)
val s = if (x > 0) 1 else -1 // preferred, val s, type: Int
// 等价于
if (x > 0) s = 1 else s = -1 // var s
-
if (x > 0) "positive" else -1 // type: Any
表达式类型为两个分支的公共超类型:Any -
if (x > 0) 1
等价于if (x > 0) 1 else ()
()看做是"无有用值"的占位符,为Unit类,相当于C++的void -
Unit
和void
的差异:void没有值("钱包是空的") Unit有一个表示"无值"的值("钱包有一张写着'没钱(写作()
)'的钞票")
2.2 语句终止 每行末尾的分号不是必须的
2.3 块表达式和赋值
- { }块表示一系列表达式,块的值是最后一个表达式的值
- 赋值语句的值是Unit类型
2.4 输入和输出
- 输出
print()
println()
printf()
- 输入
readLine()
readInt()
readDouble()
2.5 循环
-
while
语句:与C++一致 -
for
语句:for (1 <- 1 to n)
让变量i遍历<-右边表达式的所有值,遍历顺序取决于表达式类型 - 遍历字符串或数组是,你需要0到n-1的区间,此时用
until
val s = "Hello"
var sum = 0
for (i <- 0 until s.length) // i 最后一个取值是s.length - 1
sum += s(i)
// 等价于
for (ch <- s) sum += ch
-
没有
break
和continue
,实现需要通过try/catch异常机制完成
import scala.util.control.Breaks._
2.6 高级for循环/ for推导式
- 多个生成器(<-)
for (i <- 1 to 3; j <- 1 to 3) print((10*i+j) + " ") // print 11 12 13 21 22 23 31 32 33
- 每个生成器的守卫(if)
for (i <- 1 to 3; j <- 1 to 3 if i != j) print((10*i+j) + " ") // print 12 13 21 23 31 32
- 在循环中定义任意多的变量
for (i <- 1 to 3; from = 4 - i; j <- from to 3) print((10*i+j) + " ") // print 13 22 23 31 32 33
- for推导式: 构造出一个集合,每次迭代生成集合中的一个值
for (i <- 1 to 5) yield i % 3 // return Vector(1,2,0,1,2)
for (c <- "hello"; i <- 0 to 1) yield (c+i).toChar // ??? res40: String = hieflmlmop
for (i <- 0 to 1; c <- "hello") yield (c+i).toChar // ??? res41: scala.collection.immutable.IndexedSeq[Char] = Vector(h, e, l, l, o, i, f, m, m, p)
2.7 函数
def fac(n: Int) = { // 非递归函数不需要指定返回类型
var r = 1
for (i <- 1 to n) r = r * i
r // 不需要return
}
2.9 变长参数
def sum(args: Int*) = {
var res = 0
for (arg <- args) res += arg
res
}
val s1 = sum(1, 4, 9, 16, 24) // 参数为Seq类型
val s2 = sum(1 to 5) // wrong, 单个参数输入时,必须是Int类型
val s3 = sum(1 to 5: _*) // correct, 将Range当做**参数序列**处理
def recursiveSum (args: Int*): Int = {
if (args.length == 0) 0
else args.head + recursiveSum(args.tail: _*) // head:第一个元素 tail: 其他元素
}
2.10 懒值 lazy val 推迟初始化
val words = scala.io.Source.fromFile("/usr/share/dict/words").mkString // 在words被定义时即被取值
lazy val words = scala.io.Source.fromFile("/usr/share/dict/words").mkString // 在words首次使用时被取值
def words = scala.io.Source.fromFile("/usr/share/dict/words").mkString // 在每一次words被使用时取值
chapter 3. 数组相关操作
3.1 定长数组 Array
val nums = new Array[Int](10) // 长度为10,初始化为0
val a = new Array[String](10) // 长度为10,初始化为null
val s = Array("hello", "world") // 长度为2,Array[String](类型推断)已提供初始值不需要new
s(0) = "goobye" // Array("goobye", "world")
3.2 变长数组 ArrayBuffer
类似C++的vector,Java的ArrayList
import scala.collection.mutable.ArrayBuffer
val b = ArrayBuffer[Int]()
b += 1 // ArrayBuffer(1)
b += (1, 2, 3, 5) // ArrayBuffer(1, 1, 2, 3, 5)
b ++= Array(8, 13, 21) // ArrayBuffer(1, 1, 2, 3, 5, 8, 13, 21)
b ++= Seq(23, 25) // ArrayBuffer(1, 1, 2, 3, 5, 8, 13, 21, 23, 25)
b.trimEnd(7) // ArrayBuffer(1, 1, 2)
val bArr = b.toArray // Array(1, 1, 2) 转换为Array
val bArrBuf = bArr.toBuffer // ArrayBuffer(1, 1, 2) 转换为ArrayBuffer
3.3 遍历
0 until 5 // Range(0, 1, 2, 3, 4)
0 until (5, 2) // Range(0, 2, 4)
(0 until 5).reverse // Range(4, 3, 2, 1, 0)
3.4 数组转换
for推导式:for(...) yield
循环创建一个类型与原始集合相同的新集合
- 原始集合:for语句括号内的第一个生成器(<-)的表达式
for (i <- 1 to 5) yield i % 3 // return Vector(1,2,0,1,2)
for (c <- "hello"; i <- 0 to 1) yield (c+i).toChar // res40: String = hieflmlmop
for (i <- 0 to 1; c <- "hello") yield (c+i).toChar // res41: scala.collection.immutable.IndexedSeq[Char] = Vector(h, e, l, l, o, i, f, m, m, p)
- 原始集合
1 to 5
type:Range 新集合type:Vector - 原始集合
"hello"
type:String 新集合type:String - 原始集合
0 to 1
type:Range 新集合type:Vector
3.5 常用算法
很大比例的业务运算不过是在求和与排序**
It is often said that a large percentage of business computations are nothing but computing sums and sorting.
Array(1, 7, 2, 9).sum // 19 ArrayBuffer同样适用
Array("Mary", "had", "a", "little", "lamb").max // 'M' < 'a' < 'h' 'l'
val b = ArrayBuffer(1, 7, 2, 9)
val bSorted = b.sorted // ArrayBuffer(1, 2, 7, 9) ,b没有被改变
val bDesc = b.sortWith(_ > _) // ArrayBuffer(9, 7, 2, 1)
可以直接对一个 Array
排序,但不能对 ArrayBuffer
排序
val a = b.toArray
scala.util.Sorting.quickSort(a) // Array(1, 2, 7, 9)
mkString
将数组转换为字符串(类似js的join)
对于 min
max
quickSort
方法,元素类型必须支持比较操作,即带有 Ordered
trait的类型
a.mkString(" and ") // "1 and 2 and 7 and 9"
a.mkString("<", ",", ">") // "<1,2,7,9>"
3.6 实用的Scaladoc解码指环 (page.36)
3.7 多维数组
- 二维数组的类型:
Array[Array[Int]]
构造方法:ofDim
- 支持创建每一行长度不一的数组
val matrix = Array.ofDim[Int](3, 4) // 3行 * 4列
matrix(row)(col) = 42 // 访问元素
chapter 4. 映射和元组 Maps and Tuples
A classic programmer’s saying is, “If you can only have one data structure, make it a hash table.”
4.1 构造Map
- 映射(map)是对偶(pair)的集合,对偶:n=2的元组(tuple)
-
->
操作符创建pair。表达式"alice" -> 10
的值为("alice",10)
"alice" -> 10 // res11: (String, Int) = (alice,10)
- 创建Map方法
- 不可变Map
val scores = Map("Alice" -> 10, "Bob" -> 3, "Cindy" -> 8)
或者
val scores = Map(("Alice",10), ("Bob",3), ("Cindy",8))
- 可变Map
val scores = scala.collection.mutable.Map("Alice" -> 10, "Bob" -> 3, "Cindy" -> 8)
- 创建空的Map
val scores = new scala.collection.mutable.Map[String, Int]
- 不可变Map
4.2 获取Map中的值
val bobs = scores("Bob")
val bobs = scores.getOrElse("Bob", 0)
val bobs = scores.get("Bob") // res17: Option[Int] = Some(3)
val bobs = scores.get("Bobxxx") // res18: Option[Int] = None
4.3 更新Map中的值
- 对于mutable.Map
scores("Bob") = 10 // update
scores("Fred") = 7 // add
// 等价于
scores += ("Bob" -> 10, "Fred" -> 7)
scores -= "Alice" // delete
- 对于immutable.Map
val newScores = scores + ("Bob" -> 10, "Fred" -> 7)
val newScores = scores - "Alice"
4.4 迭代Map
for ((k, v) <- scores) print(k, v)
scores.keySet // res47: scala.collection.Set[String] = Set(Bob, Fred, Alice, Cindy)
scores.keys // res49: Iterable[String] = Set(Bob, Fred, Alice, Cindy)
scores.values // res48: Iterable[Int] = HashMap(3, 10, 10, 8)
for (v <- scores.values) print(v + " ")
for ((k, v) <- scores) yield (v, k) // 交换key和value位置
4.5 Sorted Map
- Map的实现:哈希表 || 平衡树,默认是哈希表
- 树形映射只能创建为immutable
val scores = scala.collection.immutalbe.SortedMap(...)
- 按插入顺序访问所有key,使用
LinkedHashMap
val months = scala.collection.mutable.LinkedHashMap("Jan"->1, "Feb"->2, "Mar"->3, "Apr"->4)
4.7 tuple
- 组元从1开始计数,用方法_1、_2、_3访问
val tp = (1, 3.14, "Fred") // tp: (Int, Double, String) = (1,3.14,Fred)
val second = tp._2 // second: Double = 3.14
val (first, second, third) = tp // first: Int = 1 second: Double = 3.14 third: String = Fred
val (first, second, _) = tp // 不关心的组元在其位置上使用_
- StringOps的
partition
方法,返回一个pair,分别包含满足和不满足某个条件的字符
"Hello World".partition(_.isUpper) // res53: (String, String) = (HW,ello orld)
4.8 zip操作
-
zip
将两个大小相等的Array一一配对成pair组成新的Array -
toMap
将pair的集合转换成Map
val symbols = Array("<", "=", ">")
val counts = Array(2, 10, 2)
val pairs = symbols.zip(counts) // Array[(String, Int)] = Array((<,2), (=,10), (>,2))
for((s, n) <- pairs) print(s * n) // <<==========>>
val pairsMap = symbols.zip(counts).toMap // pairsMap: scala.collection.immutable.Map[String,Int] = Map(< -> 2, = -> 10, > -> 2)
chapter 5. 类 Class
5.1 简单类和无参方法
class Counter {
private var value = 0 // **必须**初始化字段
def inc() { value += 1 } // 无参方法,**默认公有**
def current() = value // 无参方法+1
}
val myCounter = new Counter // 构造实例
myCounter.inc() // **改值器** 无参方法,后面加()
println( myCounter.current ) // **取值器** 无参方法,不加()
5.2 带getter和setter的属性
- 两个概念:字段(fields):对应C++的数据成员;属性(properties):字段的getter/setter
- 字段本身都是私有的。公有属性(字段声明不带private),getter/setter是公有的;私有属性(字段声明带private),getter/setter是私有的
- 对于字段xxx,其getter名为
xxx
,setter名为xxx=
println(fred.age) // 调用方法fred.age
fred.age = 21 // 调用方法fred.age=(21)
5.3 只带getter的属性 — 只读属性的val字段
5.4 对象私有字段(Object-Private)
- 与C++一样,方法默认可以访问该类的所有对象的私有字段
def isLess(other: Counter) = value < other.value // 可以访问另一个对象other的私有字段value
-
对象私有
private[this]
:以上用法将被禁止,方法只能访问当前对象(this)的私有字段value - 对象私有字段没有getter/setter
- 更细粒度的访问控制:
private[类名]
类名:当前定义的类/该类的外部类,可指定这些类的方法访问该字段
5.6 辅助构造器
- 主构造器(primary constructor)有一个,辅助构造器(auxiliary constructor)有任意多个
- 辅助构造器的名称是
this
,每个辅助构造器必须以已定义好的一个(主/辅助)构造器的调用开始 - 定义构造器:
def this(xxx) { }
注意没有等号=!
class Person {
private var name = ""
private var age = 0
def this(name: String) { // 一个辅助构造器
this() //调用主构造器
this.name = name
}
def this(name: String, age: Int) { // 另一个辅助构造器
this(name) // 调用一个辅助构造器
this.age = age
}
}
val p1 = Person()
val p2 = Person("Fred")
val p3 = Person("Petr", 20)
5.7 主构造器
- 主构造器结合于类的定义中,没有一个显式的def
- 主构造器参数直接放置在类名之后
- 主构造器执行在类定义中的所有语句
- 易知,无论是使用何种构造器,一定会执行主构造器的所有语句
class Person(val name: String, val age: Int) {
println("Just constructed another person") // 每次有对象构造出来都会执行
def this(name: String) {
this(name, 0)
}
def this(bflag: Boolean) {
this() // *出错* 定义带参数主构造器后,将没有默认的无参构造器
}
}
val p3 = new Person() // *出错* 没有无参构造器
val p1 = new Person("hello") // 输出 "Just constructed another person"
val p2 = new Person("world", 10) // // 输出 "Just constructed another person"
- 类参数中有些是字段,有些可能只是普通参数,如何区分?
- if(带var/val的参数声明) 私有字段,公有/私有的getter/setter
- if(不带var/val的参数声明) { if(被至少一个方法使用) 对象私有字段 else 普通参数 }
5.8 嵌套类
在类里面定义类,默认对象A的嵌套类和对象B的嵌套类互不相同
import scala.collection.mutable.ArrayBuffer
class Network {
class Member(val name: String) {
val contacts = new ArrayBuffer[Member]
}
private val members = new ArrayBuffer[Member]
def join(name: String) = {
val m = new Member(name)
members += m
m
}
}
val chatter = new Network // 嵌套类 chatter.Member
val myFace = new Network // 嵌套类 myFace.Member
// chatter.Member 和 myFace.Member 是两个不同的类
val fred = chatter.join("Fred")
val joe = chatter.join("Joe")
fred.contacts += joe // OK, fred 和 joe 都是chatter.Member类
val spinoza = myFace.join("Spinoza")
fred.contacts += spinoza // *出错* fred是chatter.Member类,spinoza是myFace.Member类
- 让chatter.Member和myFace.Member相同的方法
- 1.将Member类定义移到Network的伴生对象中
object Network {
class Member(val name: String) {...}
}
class Network {
private val members = new ArrayBuffer[Network.Member]
...
}
- 2.使用类型投影(type projection)
Network#Member
含义是"任何Network的Member"
class Network {
class Member(val name: String) {
val contacts = new ArrayBuffer[Network#Member]
}
...
}
- 在嵌套类使用外部类的字段:
class Network { outer =>
outer变量指向Network.this
class Network(val name: String) { outer =>
class Member(val name: String) {
def desc = name + " inside " + outer.name
}
}
chapter 6. 对象 Object
object语法的用处
- 需要某个类的单个实例
- 为其他值或函数找一个可以挂靠的地方(when you want to find a home for miscellaneous values or functions)
6.1 单例对象
- 对于C++中需要用到单例对象的地方,在Scala都可以通过定义一个object实现,作为单例模式或存放常量/方法的地方
- 除了不能为构造器提供参数以外,拥有与类一样的所有特性,可以继承类和trait
- 对象构造器在对象第一次使用时调用
object Accounts {
private var lastNum = 0
def newUniqueNum() = { lastNum += 1; lastNum }
}
Account.newUniqNum() // 当需要一个新的唯一帐号时调用, 第一次调用前构造器不会执行
6.2 伴生对象 Companion Object
- 实现类似C++的静态方法
- 同名的类和对象互为伴生,可以互相访问私有方法/字段,必须存在于同一个源文件中
class Account {
// 可以调用伴生对象的私有方法
// 这里Account.是必须的,因为伴生对象的方法不在此作用域中
val id = Account.newUniqueNum()
private var balance = 0.0
def deposit(amount: Double) { balance += amount }
}
object Accounts {
private var lastNum = 0
private def newUniqueNum() = { lastNum += 1; lastNum }
}
6.3 继承类/Trait的对象 Objects Extending a Class or Trait
- 一个对象可以继承(=1)个class和(>=0)个trait
6.4 apply方法
- 当遇到
Object(arg1, ..., argN)
时,apply方法就会被调用 - 构造器和apply方法的区别:apply不用new,Array(1, 2, 3)就是用了Array伴生对象的apply方法
-
Array(100)
和new Array(100)
的区别-
Array(100)
产生一个只有一个元素的数组Array[Int],元素值为100 -
new Array(100)
产生一个100个元素的数组Array[Nothing],值为null
-
abstract class UnitConversion(val fromUnitName: String, val toUnitName: String) {
def convert(fromUnit: Double): Double // 包含定义不完整的方法,故为抽象类
def apply(fromUnit: Double): Double = { // apply最终是在派生的object中使用
println(fromUnit.toString + " " + fromUnitName + " = " + convert(fromUnit).toString + " " + toUnitName)
convert(fromUnit)
}
}
object InchesToCentimeters extends UnitConversion("inches", "centimeters") {
// override def 重载父类方法
override def convert(fromUnit: Double): Double = 2.54 * fromUnit
}
object GallonsToLiters extends UnitConversion("gallons", "liters") {
override def convert(fromUnit: Double): Double = 3.785 * fromUnit
}
object FahrenheitToCelsius extends UnitConversion("fahrenheit", "celsius") {
override def convert(fromUnit: Double): Double = (fromUnit - 32) / 1.8
}
InchesToCentimeters(1) // 调用 InchesToCentimeters.apply(1)
GallonsToLiters(2)
FahrenheitToCelsius(86)
6.5 应用程序对象 Application Objects
Scala的Hello World程序,从一个对象的main开始,类型Array[String] => Unit
object Hello {
def main(args: Array[String]) {
println("Hello World")
}
}
继承App trait,可以不用定义main函数,直接把main代码加到object构造器方法体内,即object的定义内
原理:App extends DelayedInit(也是一个trait),所有带有DelayedInit 这个trait的类,其初始化方法都会被挪到delayedInit方法中。App trait的main方法捕获命令行参数,调用delayedInit方法。
object Hello extends App {
println("Hello World")
}
6.6 枚举
Scala没有自带枚举类型,可通过Enumeration辅助生成枚举类型
object PokerSuits extends Enumeration {
type PokerSuits = Value // 声明了该枚举类型的名字为PokerSuits.PokerSuits,若不加这行则枚举类型为PokerSuits.Value
val Spade = Value("♠") // 每个枚举量的声明,参数为枚举的名称(可选),ID(可选),默认从0开始
val Heart = Value("♥") // id=1
val Club = Value("♣") // id=2
val Dimond = Value("♦") // id=3
}
import PokerSuits._ // 使用前要先import
def isRed(suite: PokerSuits.PokerSuits): Boolean = suite == Heart || suite == Dimond
isRed(Heart) // true
Spade.id // 1
Club.toString // ♣
chapter 7. 包和引入 Packages and Imports
7.1 包 Packages
- 作用与C++的namespace一样:管理大型程序的名字。通过层层嵌套的package限定名称。
- 源文件的目录和包之间没有强制的关联关系,一个源文件可以包含多个package的定义;一个package可以分散到多个源文件中定义。
7.2 作用域规则 Scopes
- 与Java的包名总是绝对路径不同,Scala支持相对路径的包名(访问上层作用域的名称)。
package x {
object obj_top {
def fn = println("o1 func")
}
package y {
package z {
object obj {
def fn = {
println("o2 func")
obj_top.fn
}
}
}
}
package w {
object obj {
def fn = {
println("o3 func")
obj_top.fn
y.z.obj.fn
}
}
}
}
object Chap7Packages extends App {
x.w.obj.fn
}
7.3 链式包语句
以串联的形式声明package,被串联在中间的package的成员将不能用相对路径表示
package x.y {
object obj {
def fn = obj_top.fn // *错误* 位于包x的obj_top不能直接可见
def fn = x.obj_top.fn // ok
}
}
7.4 文件顶部标记法
适用于文件中所有的代码属于同一个包
package x.y
package z
object obj {}
// 以上代码等价于
package x.y {
package z {
object obj {}
}
}
7.5 包对象
- 包可以包含class、objec、trait,但不能包含函数和变量的定义
- 如何把函数和变量定义在包里面?答案:包对象
package object
- 每个包都可以有一个包对象,在父包中定义包对象,名字与对应包一样
- 包对象和包处于同一个作用域,不需要加限定前缀
package object x { val defaultName = "xxx" }
package x {
object obj_top {
def fn = println( defaultName ) // 在包里面不需要加x.
}
}
println( x.defaultName ) // 在包外面加上x. 就像是包里面直接定义的常量
7.6 包可见性
可以通过在public/protect/private加入限定包名称,控制类成员在不同包层级的可见性
package y {
package z {
object obj {
private[z] def fn = {} // 只在包z中可见
private[y] def fn2 = {} // 可见度扩展到y
}
}
}
7.7 引入 import
- 目的:可以使用更短的名称而不是长名称
import java.awt.Color._
- import 可以在代码的任意地方插入,控制import的作用域
- 重命名引入方法(避免省略长名称后可能引起的名称混淆)
import java.util.{HashMap => JavaHashMap}
- 隐藏引入方法
import java.util.{HashMap => _, _} // 隐藏HashMap,并且引入util的其他成员
- 隐式引入,每个Scala程序都隐式import如下模块:
import java.lang._
import scala._
import Predef._
chapter 8. 继承 Inheritance
8.1 扩展类 Extending a Class
class Employee extends Person {...}
可将类/方法/字段声明为 final
,确保其不能被派生/重写
8.2 重写方法 Overriding Methods
-
override
修饰符:用于重写一个非抽象方法/字段 - override必须保证方法名字和参数类型必须完全一致
- override还可以在"易违约基类问题"(fragile base class problem)时给出错误提示,即在父类引入新方法时,这个新方法与子类方法相抵触(如父类新方法名字与子类已定义的方法重名)
-
super.父类方法
用于子类调用父类的方法
8.3 类型检查和转换
-
isInstanceOf
类型检查:用于测试某个对象是否属于某个给定的类 -
asInstanceOf
类型转换:若测试成功,则用于将对象转换为子类的对象
if (p.isInstanceOf[Employee]) { // true 当p非null,且是Employee类或其子类的对象
val s = p.asInstanceOf[Employee] // s类型为Employee,若p不是一个Employee则抛出异常
}
-
if (p.getClass == classOf[Employee])
测试p是Employee对象而且不是其子类 - a better alternative, 模式匹配:
p match {
case s: Employee => ... // s 作为Employee处理
case _ => ... // p 不是Employee
}
8.4 protected字段和方法
-
protected
成员可以被子类访问,但对于类所属的包不可见——要用包修饰符才可见 -
protected[this]
类似private[this]
8.5 父类构造器
- 在子类中,只有主构造器才能调用父类构造器,而且不能像Java
super(params)
一样显式调用父类构造器 - 父类构造器的调用在子类主构造器的定义中,可任意选择父类构造器
- Scala类可以继承Java类
// 1. 定义了子类Employee
// 2. 调用了父类构造器Person(name, age)
`class Emploee(name: String, age: Int, val salavy: Double) extends Person(name, age) {...}
8.6 重写字段
- 可以在子类定义一个val重写父类中同名的val
- 重写var:仅当父类的var是抽象的才可以
8.7 匿名子类
val alien = new Person("Fred") {
def greeting = "I am alien Fred."
}
def meet(p: Person{def greeting: String}) = println(p.name + "says: " + p.greeting)
匿名子类的类型为 Person{def greeting: String}
8.8 抽象类
- 包含没有完整定义的方法或字段(称为抽象方法/抽象字段),需要定义为抽象类
abstract class
- 重写抽象方法/字段时不需要加
override
- 可以用匿名子类定制抽象字段
val fred = new Person { val id = 1729; var name = "Fred" }
8.10 构造顺序和提前定义
- 教训:有可能会在子类重写的val,不要在父类的构造器中使用该val(如用该val定义一个Array的长度)
- 涉及到Scala的子类/父类的成员构造/初始化顺序
- 构造顺序的根本原因:Java允许在父类的构造方法中调用子类的方法,C++则不允许
- "提前定义"语法 Early Definitions (略)
- The syntax is so ugly that only a mother could love it.
8.11 Scala继承层级
-
Any
整个继承层级的根节点 -
AnyVal
所有值类型的一个marker -
AnyRef
所有引用类型的父类 -
ScalaObject
所有Scala类都实现的marker接口 -
Unit
C++的void由Unit表示,只有一个值()
,AnyVal的子类,编译器允许任何值(Any
包括值和引用)被替换成()
def printUnit(x: Unit) = println(x)
printUnit("hello") // “hello"被替换成(),打印出()
-
Null
所有引用类型(AnyRef
)的子类型,唯一实例:值null
,可以赋给任何引用类型但不能赋给值类型 -
Nothing
所有类型(Any
)的子类型,没有实例。
8.12 对象相等性
- 重写
equals
方法,用来判断两个对象是否相等(不是相同) - 定义为
final
,为了父类对象(a)和子类对象(b) equals调用的对称性,i.e. a.equals(b)和b.equals(a)结果相同 - equals方法参数类型必须为
Any
final override def equals (other: Any) = {
val that = other.asInstanceOf[Item]
if (that == null) false
else desc == that.desc && price == that.price
}
Chapter 9. 文件和正则表达式
9.1 读取行/字符
import scala.io.Source
val source = Source.fromFile("myfile.txt", "UTF-8")
val source1 = Source.fromURL("http://horstmann.com", "UTF-8") // 从URL读取
val source2 = Source.fromString("hello world") // 从String读取,调试时很有用
val lines = source.getLines.toArray // 读取行
for (c <- source) {...} // 处理字符
source.buffered // 查看某个字符但不处理
...
source.close() // 处理完毕要close
9.8 序列化 Serialization
- 将对象序列化,用于传输到其他机器或临时存储
- Java通过
implements java.io.Serializable
声明一个可被序列化的类 - Scala:
@SerialVersionUID(42L) class Person extends Serializable
- Scala集合类都是可序列化的
9.9 执行Shell命令
原理:sys.process
包含一个从String到 ProcessBuilder
对象的隐士转换,!
操作符执行的是隐式转换后的 ProcessBuilder
对象
import sys.process._
"ls -al .." ! // 执行结果打印到标准输出,返回结果:被执行命令的返回值,成功0
val res = "ls -al .." !! // 执行结果以字符串形式返回
"ls -al .." #| "grep sec" ! // 管道
还有重定向 #>
#>>
#<
,以及 p #&& q
(如果p成功则执行q) p #|| q
(如果p失败则执行q)
9.10 正则表达式
val numPattern = "[0-9]+".r
val wsnumwsPattern = """\s+[0-9]+\s+""".r // 用"""不需要转义反斜杠
val matches = numPattern.findAllIn("99 bottles, 98 bottles").toArray // Array(99, 98)
val m1 = wsnumwsPattern.findFirstIn("99 bottles, 98 bottles").toArray // Some(" 98 ")
还有方法:findPrefixOf
replaceFirstIn
replaceAllIn
(略)
9.11 正则表达式组
val numitemPattern = "([0-9]+) ([a-z]+)".r
val numitemPattern(num, item) = "99 bottles" // num: String = 99, item: String = bottles
for (numitemPattern(num, item) <- numitemPattern.findAllIn("99 bottles, 98 bottles")) println((num, item))