Learning Scala学习笔记

book notebook : Learning Scala

Basic

1.String interpolation(字符串插值): Building a String based on other values is reasonably easy to do with string addition.

1
2
3
4
5
6
7
val approx = 355/113f
s"Pi, using 355/113, is about $approx."

val item = "apple"
s"How do you like them ${item}s?"

s"Fish n chips n vinegar, ${"pepper "*3}salt"

字符串插补,最开始加上s,在双引号字符串中用$引用变量名.或者用${}来区分上下文,防止混入其他字符.

2.Tuples: can be useful when you need to logically group values, but there is no way to iterate through elements in a tuple.
access an individual element from a tuple by its 1-based index
creating a 2-sized tuple use the relation operator (->), it’s a shortcut for representing key-value pairs in tuples

1
2
val red = "red" -> "0xff0000"
//red: (String, String) = (red,0xff0000)

3.Matching: like java switch,a single input item is evaluated and the first pattern that is “matched” is executed and its value returned.

1
2
3
4
5
6
val day = "MON"
val kind = day match {
case "MON" | "TUE" | "WED" | "THU" | "FRI" => "weekday"
case "SAT" | "SUN" => "weekend"
case _ => "unknown"
}

pattern guard:

1
2
3
4
5
val response: String = null
response match {
case s if s != null => println(s"Received '$s'")
case s => println("Error! Received a null response")
}

Matching Types with Pattern Variables: match the type of the input expression.
Pattern variables, if matched, may convert the input value to a value with a different type.

1
2
3
4
5
6
7
val x: Int = 12180
val y: Any = x
y match {
case x: String => s"'x'"
case x: Int => s"${x}i"
case _ => "unknown type"
}

match表达式能够匹配值的真正类型(Int),而不是声明的类型(Any).

4.for-comprehension:

1
2
3
for (x <- 1 to 7) { println(s"Day $x:") }
val days = for (x <- 1 to 7) yield { s"Day $x:" }
for (day <- days) print(day + ", ")

第二种和map类似,对输入的每个元素运用表达式(map则使用函数),结果是一个和输入个数一样的集合.

Iterator Guards: filter, adds an if expression to an iterator.

1
2
3
4
5
6
7
8
9
val threes = for (i <- 1 to 20 if i % 3 == 0) yield i           //Iterator Guards
val quote = "Faith,Hope,,Charity"
for {
t <- quote.split(",")
if t != null //Iterator Guards
if t.size > 0
}{ println(t) }

val powersOf2 = for (i <- 0 to 8; pow = 1 << i) yield pow //value binding

Recursive Functions with tail-recursion 尾递归优化:
好处是: iterating without using mutable data 不使用可变变量就可以完成迭代,并且不会增加栈空间.

1
2
3
4
def power(x: Int, n: Int): Long = {
if (n < 1) 1
else x * power(x, n-1)
}

上面不是尾递归,因为最后一个语句虽然有递归调用,但结果还是一个statement,而不是recursive call.

1
2
3
4
5
@annotation.tailrec
def power(x: Int, n: Int, result : Int = 1): Long = {
if (n < 1) 1
else power(x, n-1, x * result)
}

5.Parameter Groups:break a list of parameters into groups of parameters, each separated with their own parentheses.

1
2
3
4
def max(x: Int, y:Int) = if (x > y) x else y

def max(x: Int)(y: Int) = if (x > y) x else y
max(1,2)

6.Type Parameters: pass type parameters, which dictate the types used for the value parameters or for the return value.

1
2
3
def identity(a: Any): Any = a
val s: String = identity("Hello")
//type mismatch; found : Any required: String

参数类型是String,可以转为Any.但是结果类型无法转换为Any. 解决方式:不使用指定类型或根类型,而是parameterize the type

1
2
3
def identity[A](a: A): A = a
val s: String = identity[String]("Hello")
val d = identity(2.717)

函数名称后面的[A]指定了类型, 参数和返回结果可以都是这个类型
function’s type parameter is A, It is used to define the type of the value parameter a and the return type of the function.

7.Function Type: the type of a function, make of input parameter types and return value type.

1
2
3
4
5
6
def double(x: Int): Int = x * 2         // creating a function
val myDouble: (Int) => Int = double // assign the function to a function value
//myDouble: Int => Int = <function1> // 只有一个参数,可以省略括号, function1也表示只有一个参数

val myDouble = double _ // with Wildcard, for a future invocation of the function
val amount = myDouble(20)

函数的签名包括了: 函数名称, 输入类型, 输出类型. 那么除了函数名称外, 用输入和输出类型来表示函数的类型最合适不过了.
函数值:包括了函数名称和函数类型,myDouble是函数名称,(Int) => Int是函数类型.和普通变量val a : Int类似(变量名:类型).
变量值也是用val, 函数值同样用val. 因此赋值给一个变量,和一个函数都要声明它的类型.

注意:只有使用val声明一个函数值时,才使用(InputType) => OutputType. 比如将一个函数赋值给一个函数值.

1
2
3
4
5
def logStart() = "Starting now..."      //这是一个普通的函数
logStart() //可以直接调用这个函数

val start: () => String = logStart //或者将这个函数赋值给一个函数值,需要指定(InputType)=>ReturnType函数类型
start() //对函数值可以像普通函数的调用方式一样调用

8.Higher-Order Function(高阶函数): a function that has a value with a function type as an input parameter or return value.
函数的输入类型或者输出类型是一个函数类型. 我们已经知道函数类型是由输入类型和输出类型用=>组成.
函数的参数是一个函数, 或者函数的返回值是一个函数. 函数作为参数时,当然要传递一个函数,作为返回值时可以被再次调用.

1
2
3
4
5
def safeStringOp(s: String, f: String => String) = {
if(s != null) f(s) else s
}
def reverser(s: String) = s.reverse
safeStringOp("scala", reverser)

reverser是一个函数,把f这个函数值作为safeStringOp函数(这是一个高阶函数)的一个参数,f(s)就跟普通函数的调用方式一样.

1
2
3
4
def reverser(s: String) = s.reverse     //定义一个普通函数
val f: String => String = reserver //创建一个函数值, 将普通函数赋值给f
safeStringOp("scala", f) //f的类型是String => String,正好对应了safeStringOp的第二个参数类型
def safeStringOp(s: String, f: String => String) = ... //函数的参数由参数名称: 参数类型组成.

9.Function Literals: 也叫做匿名函数(没有函数名称,没有使用def定义一个函数),lambda表达式.

1
2
3
4
5
6
val doubler = (x: Int) => x * 2         //函数字面量
//doubler: Int => Int = <function1> //可以看到和前面的函数值一样

val greeter = (name: String) => s"Hello, $name" //箭头右边是一个表达式,但是它是带有参数的
val maximize = (a: Int, b: Int) => if (a > b) a else b
val start = () => "Starting NOW"

等号右边的才是函数字面量的定义, 而左边的val doubler和正常的变量定义一样. (x: Int) => Int是一种函数类型.
所以函数字面量等价于: 1.声明一个普通函数, 2.将函数赋值给一个函数变量. 下面的代码和上面的是等价的.

1
2
3
4
5
6
7
8
def double(x: Int): Int = x * 2         //1.创建一个普通函数
val doubler : Int => Int = double //2.将函数赋值给一个函数值(变量的类型是函数类型)

def max(a: Int, b: Int) = if (a > b) a else b
val maximize: (Int, Int) => Int = max

def logStart() = "Starting NOW"
val start: () => String = logStart

如果要把函数字面量作为高阶函数的调用参数, 可以连变量名称都用需要了.
在高阶函数中我们看到了正常的调用方式: 声明一个函数值,因为它是一个函数类型,所以可以作为高阶函数的参数.

1
2
3
def reverser(s: String) = s.reverse 
val f: String => String = reserver
safeStringOp("scala", f) //f是一个函数值,作为高阶函数的传入参数

现在前面两句可以直接用一个函数字面量来表示:

1
2
3
4
5
val f = (s: String) => s.reverse                //这是一个函数字面量,等价于上面的前两条语句

safeStringOp(null, (s: String) => s.reverse) //省略掉val f =, 可以直接作为参数传入
safeStringOp("Ready", s => s.reverse) //或者高阶函数中定义了类型,这里的函数字面量可以省略类型和括号
safeStringOp("Ready", _.reverse) //占位符

还有几种写法, 比如函数值可以用block块包含, 或者将第一个参数和第二个参数用参数组分开.

1
2
3
4
5
6
7
8
def safeStringOp(s: String)(f: String => String) = {
if (s != null) f(s) else s
}
val timedUUID = safeStringOp(uuid) { s =>
val now = System.currentTimeMillis
val timed = s.take(24) + now
timed.toUpperCase
}

10.函数类型,函数值,高阶函数,函数字面量,都是输入类型到输出类型.

Function Syntax Example
函数类型 (InputType) => OutputType String => String
函数值 val f: (InputType) => OutputType = function val f: String => String = reverser (reverser是一个普通函数)
高阶函数参数 f: (InputType) => OutputType f: String => String
函数字面量 val f = (input: InputType) => OutputType val f = (s: String) => s.reverse

下面总结下几种写法

1
2
3
4
5
6
7
8
9
def double(x: Int): Int = x * 2             // 普通函数
val doubler: Int => Int = double //① 函数值

val doubler = (x: Int) => x * 2 //② 函数字面量

val doubler: Int => Int = _ * 2 //③ 带占位符

val doubler: (x: Int) => Int = x * 2 //Error
val doubler: Int => Int = (x: Int) => x * 2 //OK,但是可以省略掉类型 : Int => Int,变成②

小结:如果函数作为高阶函数的参数,比如②,则可以省略掉变量名,只剩下(x: Int) => x * 2, 或者再用占位符只剩下_*2
高阶函数的参数除了函数,通常会有一个和函数的输入参数类型匹配的参数. 比如safeStringOp(s: String, f: String => String)
这样函数_.reverse就可以直接作用于第一个参数的内容. safeStringOp("Ready", _.reverse)表示对”Ready”运用reverse函数

1
2
def combination(x: Int, y: Int, f: (Int,Int) => Int) = f(x,y)
combination(12, 23, _ * _) //两个占位符,分别对应了x,y两个输入参数,因为函数作用的顺序是f(x,y)

高阶函数可以结合泛型(type parameters):

1
2
3
4
def tripleOp[A,B](a: A, b: A, c: A, f: (A, A, A) => B) = f(a, b, c)
tripleOp[Int,Int](1, 2, 3, _ * _ + _)
tripleOp[Int,Double](1, 2, 3, 1.0 * _ / _ / _)
tripleOp[Int,Boolean](1, 2, 3, _ + _ > _)

11.Currying:

reuse a function invocation and retain some of the parameters to avoid typing them in again. you can
partially apply the function by using the wildcard operator to take the place of one of the parameters

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def factorOf(x: Int, y: Int) = y % x == 0
//(Int, Int) => Boolean
val f = factorOf _ //不保留任何参数
//f: (Int, Int) => Boolean = <function2> //factorOf有两个参数都是Int类型, 返回类型是Boolean
val x1 = f(3, 15) //调用时,必须指定所有的参数
val x2 = f(3, 20)

val multipleOf3 = factorOf(3, _: Int) //保留了一个参数x, 第二个参数y使用占位符表示,需要指定类型
//multipleOf3: Int => Boolean = <function1> //返回的函数的输入类型对应占位符的类型, 返回类型和factorOf类型一样
val y1 = multipleOf3(15) //现在调用multipleOf3方法只需要一个参数
val y2 = multipleOf3(20)

def factorOf(x: Int)(y: Int) = y % x == 0 //使用参数组(multiple parameter lists),只运用/保留其中的一个参数
//Int => Int => Boolean
val isEven = factorOf(2) _ //factor(3, _: Int)相比分成了factor(3) _ 看起来更加简洁
//isEven: Int => Boolean = <function1>
val z1 = isEven(1)
val z2 = isEven(2)

12.By-Name Parameters: 命名参数, 既可以传值,也可以传函数. 传函数时,每次使用by-name parameter都会调用这个传入的函数.

1
2
3
4
5
6
7
def doubles(x: => Int) = {
println("Now doubling " + x)
x * 2
}
doubles(5) //regular value. 没有副作用
def f(i: Int) = {println(s"Hello from f($i)"); i}
doubles(f(5)) //传入的是一个函数, 有副作用. 因为在doubles中每次使用x都相当于调用了一次f(5)

如果在命名参数所在的函数中,只使用了一次命名参数, 那么传入值和函数就都没有副作用了.

1
2
3
4
5
6
7
8
9
10
11
def timer[A](f: => A): A = {
def now = System.currentTimeMillis //这里定义的是函数,而不是一个只,是为了可以多次调用这个函数
val start = now
val a = f //如果传入的是一个函数, 则这里的a是一个函数值, 因为我们将一个函数赋值给变量a
val end = now
println("cost ${end - start} ms")
a //调用这个函数(如果是函数的话), 或者返回值(对于值,直接返回,计算耗时没有什么意义)
}
val doWhatYouWant = timer { //只剩下函数名和block. block可以是表达式块,或者函数字面量块
//这里是一个函数块,它的返回值就是doWhatYouWant. 这样就具有了统计函数执行耗时的功能
}

f: => A, 传入的函数的返回值类型一定是A. 所以最后一句调用函数的返回值的类型也是timer的返回类型A
timer是个高阶函数,可以延伸到: 管理数据库连接事务(打开会话,调用函数,关闭事务), 错误重试, 根据上下文条件选择调用

Collection

1.List的构造以及Nil

a List will always end with Nil. List总是以Nil结束的.

1
2
3
4
5
val l: List[Int] = List()       //创建一个空的List,返回的是Nil
l == Nil //true

val m: List[String] = List("a") //创建只有一个元素的List
m.tail == Nil //列表的尾巴指向了Nil

Cons操作符(::)创建列表, 最后面必须是一个List, 因为Nil是一个List,所以可以跟在最后面

1
2
3
4
5
6
val numbers = 1 :: 2 :: 3 :: Nil    //OK
val errors1 = 1 :: 2 :: 3 //Wrong
val errors2 = Nil :: 1 :: 2 :: 3 //Wrong

val first = Nil.::(1)
val second = 2 :: first //最右边必须是一个List, 而first正是一个List

::是List的一个方法,所以1 :: Nil等价于 Nil.::(1),所以::属于右结合操作符. 参数1会成为List的head.
注意: 不要因为::是右结合性, 就认为上面的numbers的返回是3,2,1. 实际上List的::方法会将参数作为head.
可以从右边到左边画一个箭头, 左边的每个元素会作为右边列表的head.
比如3作为Nil的head. 2作为3::Nil的head. 1作为2::3::Nil的head.

1
2
3
4
1 :: 2 :: 3 :: Nil
1) Nil.::(1::2::3), 1::2::3整体作为List的head, ==> List(1::2::3, Nil)
2) 继续分析1::2::3 ==> List(3).::(1::2), 1::2作为List的head ==> List(1::2, 3, Nil)
3) 继续分析1::2 ==> List(2).::(1) ==> List(1, 2, 3, Nil) ==> List(1, 2, 3)
  • 两个List相加用:::, 单个元素和List相加用::, 其中List必须在最后面
  • 多个List压扁成一个List用flatten, List(List(1),List(2,3),List(4,5,6)).flatten => List(1,2,3,4,5,6)
  • 对一个列表分组的方式有: partition根据条件分组, splitAt根据索引切片
  • zip一一合并两个List通常需要两个List的大小一样,返回tuple,分别来自于两个List
  • 对列表的前面操作要比在后面操作要高效,因为列表是linked-list,访问列表后面的元素需要遍历列表

2.flatMap指的是先map再flatten, 而不是反过来!

1
2
3
4
5
6
List("milk,tea","hello,world") flatMap (_.split(","))
1) 先对List的每个元素进行map, 变成List(List(..), List(..))
"milk,tea".split(",") = List("milk","tea")
"hello,world".split(",") = List("hello", "world")
2) 在对List(List("milk","tea"), List("hello", "world")) 进行flatten得到一个List
List("milk","tea", "hello", "world")

3.自己实现contains reduce函数: 使用累加变量,包含当前结果,并基于当前元素更新变量值

1
2
3
4
5
6
7
8
9
def contains(x: Int, l: List[Int]): Boolean = {
var exists: Boolean = false //假设一开始是不存在的
for(i <- l) {
if(!exists) { //不存在的情况下
exists = (i == x) //比较列表当前元素i和要比较的元素x是否相等, 复制给一开始声明的变量exists
} //如果当前元素和x不相等, exists还是false, 继续处理list的下一个元素
} //而如果相等, exists=true,就不会再执行if中的语句了,以后也都不会了!
exists //最终返回的exists表示列表中是否存在x
}

将包含的逻辑抽取出来,形成更通用的boolReduce:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def boolReduce(l: List[Int], start: Boolean)(f: (Boolean, Int) => Boolean): Boolean = {
var a = start //初始值提升到函数参数中
for(i <- l) a = f(a, i) //循环中要更新累加变量a
a //最后返回变量
}
val included = boolReduce(List(1,2,3), false) { (a,i) =>
if(a) a //初始值a=start=false, 传给f(a,i), 进入这里的处理逻辑. 因为false,所以处理else部分
else (i == 2) //i实际上是for(i<-l)中的当前元素,判断是否和2相等,返回值是boolean. 最终赋值给a=f(a,i)
} //即f(a,i)会真正调用到这里的具体逻辑,并将结果赋值到boolReduce中的a,用于下一次f(a,i)的a

val allLarge2 = boolReduce(List(3,4), true) { (a,i) =>
if(a) (i > 2)
else false
}

在抽取成更通用的reduce操作(函数体没有任何变化,只有类型发生了变化):

1
2
3
4
5
6
7
8
9
10
11
def reduceOp[A,B](l: List[A], start: B)(f: (B, A) => B): B = {
var a = start
for(i <- l) a = f(a, i)
a
}

val sumAnswer = reduceOp(List(1,2,3), 0)(_ + _)
val max = reduceOp(List(3,2,1), 0){ (a,i) =>
if(i > a) i
else a
}

4.可变集合的构建方式(添加一个元素用+=, 删除元素用-, 使用++=添加多个元素):

  • 使用mutable collections. List->mutable.Buffer, Set->mutable.Set, Map->mutable.Map
  • 将不可变集合转换为可变集合, 使用toBuffer. 转回不可变使用toList,toSet,toMap
  • Collection Builders:使用newBuilder[T]创建可变,使用result返回不可变结果.不适合在构建时有迭代操作.

5.其他集合类型:

  • Seq包括IndexedSeq和LinearSeq. 带有索引的Seq是可以根据索引号直接访问元素,线性Seq会依次遍历Seq.
  • Stream是lazy集合,只有在计算的时候,才会将计算结果添加到集合中. 使用Stream.cons或者#::.
  • Option集合最多包含一个元素,如果没有元素则为None,存在一个元素会被包装成Some. 可以更安全地执行.
  • util.Try结果可能是包含了返回值的Success或者包装了异常信息的Failure. Try接受函数参数()或者表达式块{}
1
2
3
4
5
6
7
8
9
10
11
12
13
def nextOption = if (util.Random.nextInt > 0) Some(1) else None
val myOption = nextOption
myOption match {
case Some(x) => x
case None => -1
}

def nextError = util.Try{ 1 / util.Random.nextInt(2) }
val myError = nextError
myError match {
case util.Success(x) => x
case util.Failure(error) => -1
}

Option和util.Try都是Collection,所以也拥有集合操作的一些常用方法,比如flatMap,foreach等:

1
2
3
4
5
6
7
8
9
10
11
val input = "    123   "
val result = util.Try(input.toInt) orElse util.Try(input.trim.toInt)
result.foreach{r => println(s"Parsed '$input' to $r!")}
val x = result match{
case util.Success(x) => Some(x)
case util.Failure(ex) => {
println(s"Can't parse input '$input'")
None
}
}
val y = result.toOption //也可以直接将Try结果转换成Option.

使用了orElse将一个Try转成另一个Try(当第一个Try解析失败的时候,会使用第二个Try:去掉空格后再转成Int)

6.A future is a monitor of a background Java thread in addition to being
a monadic container of the thread’s eventual return value. 监视后台java线程,未来返回值的容器
创建Future,需要指定当前会话的上下文,默认全局:import concurrent.ExecutionContext.Implicits.global
使用Future的sequence方法同时执行多个Future(将所有Future用List表示),返回值是每个Future结果值组成的List.

1
2
3
4
5
6
7
8
9
10
import concurrent.ExecutionContext.Implicits.global
import concurrent.Future

val cityTemps = Future sequence Seq( //要同时执行两个Future异步任务
Future(cityTemp("Putian")), Future(cityTemp("Hangzhou"))
)
cityTemps onSuccess {
case Seq(x, y) if x > y => println(s"Putian is warmer: $x °")
case Seq(x, y) if x < y => println(s"Hangzhou is warmer: $y °")
}

Class

  • class上可以跟上参数, 字段(可变val或不可变var). 参数在创建后不可以访问, 而field可以访问
  • 子类继承父类, 重写父类的方法时, 要添加override. 访问当前类的方法用this,访问父类的方法用super
  • 子类可以赋值给父类, 反过来父类不能赋值给子类. 不同类型组成的List类型是最小公共类,通常是父类
  • 带有参数或字段的子类继承父类, 需要指定父类的参数或字段.
  • 类的输入参数可以有默认值, 并且可以通过名称指定,不需要保证参数的顺序
1
2
3
4
5
6
class Car(val make: String, var reserved: Boolean) {
def reserve(r: Boolean): Unit = {reserved = r}
}
class Lotus(val color: String, _reserved: Boolean) extends Car("Lotus", _reserved)
val l = new Lotus("Silver", false) //第二个参数会作为父类的可变字段
println(s"Requested a ${l.color} ${l.make}") //不能访问Lotus的reserved参数,因为它不是字段! 但可以访问父类的reserved字段

Car的field有两个,分别是不可变的make和可变的reserved,因为reserved字段可变,所以可以在reserve方法中修改
Lotus的_reserved是参数(而不是字段), 在继承父类时,要指定父类的输入参数(虽然父类两个输入参数都是字段)

  • Type Parameters
1
2
3
4
5
class Singular[A](element: A) extends Traversable[A] {
def foreach[B](f: A => B) = f(element) //foreach接收一个函数,所以它是一个高阶函数
}
val p = new Singular("Planet")
p.foreach(println) //实际上会调用println(element)=pritln("Planet")
  • 抽象类的方法如果没有具体实现,不需要使用abstract关键字. 字段没有初始化,则子类需要提供类参数.
  • 抽象类和子类的转换, 父类和子类的转换是一样的: 子类可以声明/转换为父类类型
1
2
3
4
5
6
7
8
9
10
11
abstract class Car {
val year: Int
val automatic: Boolean = true
def color: String
}
class RedMini(val year: Int) extends Car {
def color = "Red" //实现抽象类,必须实现抽象类的所有方法
}
class Mini(val year: Int, val color: String) extends Car //可以用vaue字段来实现抽象方法color,因为color没有参数

val redMini: Car = new Mini(2015, "Red") //Mini继承了Car, 所以可以声明为父类Car这个类型
  • 抽象类不能实例化,但是如果要new抽象类,代码块中是匿名类,也要实现了抽象类的方法
  • 匿名类不能重用,也就是说要定义一个新的Listener对象,需要重新实现不同的trigger逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Listener { def trigger }
class Listening{
val listeners = collection.mutable.Buffer[Listener]()
def register(l: Listener) {
listeners += l
}
def sendNotification(){
for(l <- listeners) l.trigger
}
}
val notifications = new Listening()
notifications.register(new Listener{
def trigger {
println("1.trigger")
}
})
notifications.register(new Listener{
def trigger {
println("2.trigger")
}
})
notifications.sendNotification
  • 访问控制符, protected:当前类和子类可以访问, private:仅当前类可以访问
  • class method通常是基于instance对fields进行读写,而object method通常是pure function或者IO相关,和instance无关
  • companion object和class可以互相访问私有的成员变量. 伴生对象的apply方法通常作为工厂类负责创建class instance
  • 对于数据存储,使用case class优先于class, 对于有很多函数(不是方法!), 使用object优先于class
  • trait类似于抽象类或Object(可以有具体实现), 如果继承了trait,还是用extend,继承多个trait,后面的trait用with
  • trait的层级关系是从右到左, 虽然一个类语法上提供了继承多个trait,但是实际上是通过层层继承实现的
  • trait没有class parameter.在trait中使用self type,类似于继承了父类,如果父类有class param,是不需要指定的
1
2
3
4
5
6
class A { def hi = "hi" }
trait B { self: A => //使用self type指定type为A,类似于B extends A
override def toString = "B:" + hi //因为B'继承'了A,所以可以调用父类A的方法
}
class C extends B //Error! B只能混入(mix into)到A的子类中. 是说类继承B,必须同时继承A??
class C extends A with B //OK,C是A的子类,因为B可以混入,实际顺序是: C->B->A,这里看出B也继承了A

注意:trait直接继承有参数的class,是不能指定参数值的!

1
2
3
class RGBColor(color: Int) { def hex = f"$color%06X" }
trait A extends RGBColor //OK
trait A extends RGBColor(1) //ERROR! parents of traits may not have parameters

但是它可以在子类(Paint)和父类(RGBColor)的之间(Paint->Opaque->RGBColor),添加一些额外的功能(重写方法):

1
2
3
4
class RGBColor(val color: Int) { def hex = f"$color%06X" }
trait Opaque extends RGBColor { override def hex = s"${super.hex}FF" }
class Paint(color: Int) extends RGBColor(color) with Opaque
val red = new Paint(128 << 16).hex //Paint.hex->Opaque.hex, 通过super.hex调用了RGBColor.hex, 将其返回结果再加上FF, 最终返回.

上面的Paint的参数会作为RGBColor的参数, Opaque在两者中间, 但参数还是会传递过去的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TestSuite(suiteName: String) {
def start(){}
}
trait RandomSeed { self: TestSuite => //如果为了调用TestSuite.start,而且是继承了TestSuite类,需要传入参数,但是trait是不能加参数的
def randomStart(){
util.Random.setSeed(System.currentTimeMillis)
self.start() //使用self type,类似于继承,还是可以访问到TestSuite.start方法
}
}
class IdSpec extends TestSuite("ID Tests") with RandomSeed {
override def start(){
println("start....")
}s
println("starting.....")
randomStart() //RandomSeed.randomStart->通过self type,RandomSeed访问->TestSuite.start,由于IdSpec重写了start方法,所以调用IdSpec.start
}

self type可以用于dependency inject,即在实例化instance的时候,才注入指定的trait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User(val name: String){
def suffix = ""
override def toString = s"$name$suffix"
}
trait Attorney { self: User =>
override def suffix = ", esq."
}
trait Wizard { self: User
override def suffix = ", Wizard"
}
trait Reverser {
override def toString = super.toString.reverse
}
val h = new User("Harry P") with Wizard
val g = new User("Ginny W") with Attorney
val l = new User("Luna L") with Wizard with Reverser //User->Reverser->Wizard.

一种替代方式是创建三个类比如UserAttorney extends User with Attorney, UserWiRe extends User with Wizard with Reverser
即定义不同的类,将class和trait组合在一起. 但是这种方式除了原始的class,trait,又会有很多个class. 而使用instantiation traits避免创建很多类.


Advance Type

Implicit Parameter(隐式参数): Functions can define an implicit parameter, 函数可以定义一个隐式参数
often as a separate parameter group from the other nonimplicit parameters. 通常和非隐式参数分开
Invokers can then denote a local value as implicit 调用者声明一个本地值作为隐式(implicit val)
so it can be used to fill in as the implicit parameter. 这个本地值会填充到隐式参数中
When the function is invoked without specifying a value for the implicit parameter, 函数调用时没有为隐式参数传参
the local implicit value is then picked up and added to the function invocation. 本地的隐式值自动加入到函数调用中

1
2
3
4
5
6
7
8
9
10
11
12
13
object Doubly{                      //函数定义在object中,是为了和调用者所在的namespace隔离
def print(num: Double)(implicit fmt: String) ={
println(fmt.format(num))
}
}
Doubly.print(1.234) //Error! 没有隐式值,必须指定所有的参数,才可以正常调用一个函数
Doubly.print(1.234)("%.1f") //OK! 显示调用函数

case class USD(amount: Double) {
implicit val printFmt = "%.2f" //添加一个implicit local value, 变量名不是很重要
def printMe = Doubly.print(amount) //上面声明的隐式值,会自动作为隐式参数,传入到Doubly.print函数
}
USD(12.1234).printMe

Implicit Class(隐式类): providing an automatic conversion from instances of type A to type B,
an instance of type A can appear to have fields and methods as if it were an instance of type B.
隐式类将参数的类型A,自动转换为声明的隐式类B. 就好像类型A也有了类型B的所有字段和方法: 将类型B的方法添加到A上.

1
2
3
4
5
6
7
object IntUtils{
implicit class Fishies(val x: Int) { //隐式类,Int类型会自动转换为Fishies
def fishies = "Fish" * x //所以Int类型的变量就都有了Fishies的变量和方法
}
}
import IntUtils._ //添加到当前namespace
println(3.fishies) //因为隐式类型转换, Int类型也拥有了Fishies的fishies方法

隐式类的特点:

  • 必须被定义在object,class,trait中. 定义在object中可以方便地import到当前namespace
  • 必须仅有一个nonimplicit class参数, 比如上面的val x: Int,表示将Int转换为Fishies,来获得fishies方法
  • implicit class名称在当前ns中不能其他object,class,trait冲突(case class不能作为隐式类,因为它会自动生成伴生对象)

隐式类的参数是会被添加其他方法的类型instance,而隐式类名称不重要,只要保证唯一即可.
比如我们要给Int类型的变量添加其他方法,我们不能在已有的Int中添加,所以可以随便给一个隐式类,定义需要的方法
而且隐式类的参数就是Int类型变量,这样在隐式类中定义的函数,以类参数int变量基础,加上自定义的方法.

scala.Predef中的members会自动添加到当前namespace,除此之外,需要手动import隐式类,才会提供隐式转换的功能.

1
2
3
implicit class ArrowAssoc[A](x: A) {
def ->[B](y: B) = Tuple2(x, y)
}

1 -> "a"为例,1是Int类型,表达式等价于1.->(“a”),即int变量自动拥有了->方法.
即Int类型被自动隐式转换为了ArrowAssoc类型,所以Int类型的变量页都拥有ArrowAssoc类型的所有方法

Type Alias(类型别名): 通过已有的类型,构造自己的类型

1
2
3
4
5
6
7
class User(val name: String)
trait Factory[A] {
def create: A
}
trait UserFactory extends Factory[User] {
def create = new User("")
}

Bounded Type(边界类型): 上界[A <: Parent]指的是A必须是Parent或其子类(no upper than Parent)
下界[A >: Child]指的是A必须是Child或其父类(no lower than Child).

1
2
3
4
class BaseUser(val name: String)
class Admin(name: String, val level: String) extends BaseUser(name)
class Customer(name: String) extends BaseUser(name)
class PreferredCustomer(name: String) extends Customer(name)

上面几个类的关系如下:

1
2
3
                        Admin     -- 
| --> BaseUser
PreferredCustomer --> Customer --

[A <: BaseUser],则三个子类都满足. [A >: Customer],只有Customer和BaseUser满足.

边界类型可以和抽象类一起使用. 不同的子类使用不同的类型.

1
2
3
4
5
6
7
8
9
10
11
12
abstract class Card {
type UserType <: BaseUser
def verify(u: UserType): Boolean
}
class SecurityCard extends Card {
type UserType = Admin
def verify(u: Admin) = true
}
class GiftCard extends Card {
type UserType = Customer
def verify(u: Customer) = true
}

Type Variance(协变类型):

scala的多态,允许将lower类型存储为higher类型:

1
2
3
4
5
class Car { override def toString = "Car()" }
class Volvo extends Car { override def toString = "Volvo()" }
class VolvoWagon extends Volvo

val c: Car = new Volvo()

但是对于type parameter(泛型)却是不适用的.

1
2
3
case class Item[A](a: A) { def get: A = a }
val c: Item[Car] = new Item[Volvo](new Volvo) //error: type mismatch, found : Item[Volvo], required: Item[Car]
def item(v: Item[Volvo]) { val c: Car = v.get } //instance是Item[Volvo],但是返回值是Car类型,说明可以将Item[Volvo]转为Item[Car]

给类型参数添加+协变(covariant),则子类可以转变/改变成父类:

1
2
case class Item[+A](a: A) { def get: A = a }
val c: Item[Car] = new Item[Volvo](new Volvo) //ok now

方法的参数需要使用逆变(contravariant):

1
2
3
4
class Check[A] { def check(a: A) = {} }         //type parameter invariant, 则方法的类型和类的类型必须一致
def check(v: Check[Volvo]) { v.check(new VolvoWagon()) } //子类实例可以传入到父类类型的参数

class Check[-A] { def check(a: A) = {} } //逆变

文章目录
  1. 1. Basic
  2. 2. Collection
  3. 3. Class
  4. 4. Advance Type