[关闭]
@spiritnotes 2016-03-22T13:48:18.000000Z 字数 12173 阅读 2076

《快学Scala》

Scala


1 基础

Scala中使用方法而不是强制类型转换来做数值类型之间的转换
没有++,--因为Int类是不可变的
a 方法 b 与 a.方法(b) 一样
数学函数位于 scala.math 中,可以像函数一样使用,实际上应该是伴生对象的方法
一般情况下,没有参数并不改变当前对象的方法不带圆括号

apply
使用伴生对象的apply方法是scla中构建对象的常用方法

2 控制结构和函数

条件表达式是有值的,可以分支的类型不一样,其结果类型为分支的公共超类型。

  1. :paste 用以REPL中粘贴多行代码,避免短视
  2. if (x>0) 1 else -1
  3. if (x>0) 1 // if (x>0) 1 else ()

跨行:可以使用括号,未结束的操作符

块语句是指位于 {} 中的语句序列,包含一系列表达式,结果也是表达式,最后一个表达式的值就是语句块的值

赋值本身是没有值的,其值是 Unit 类型的 () 值

输入输出:print println printf, readLine readInt

  1. while / do while 循环:
  2. while (n>0){...}
  3. for 循环
  4. for (i <- 1 to 6) ..
  5. for (i <- 1 to 6; from = 4-i;j <- from until 9
  6. if i!=j ;)
  7. for (...) yield {}

对于递归函数,必须指定返回类型

默认参数,带名参数与Python接近

变长参数: def sum(args:Int*) ...
使用: sum(1 ot 5:_*)

函数体包含在花括号中但没有等号的返回类型是Unit,称为过程,不返回值,调用它只是为了副作用

当val被声明为lazy时,初始化将被推迟,直到首次对它取值
懒值也有开销,访问时都有线程安全方式检查该值是否已经被初始化

  1. val words = //当时初始化
  2. lazy val wrods = //第一次使用时初始化
  3. def words = //每次使用时均求值

异常表达式的类型是Nothing,在if/else中,则返回类型是非Nothing分支的类型
异常捕获使用模式匹配
tray catch finally

3 数组相关操作

若长度固定使用Array,可能有变化使用ArrayBuffer

  1. val nums = new Array[Int](10)
  2. val nums2 = Array(1,2,3);nums(0)=4
  3. import scala.collection.mutable.ArrayBuffer
  4. val b = ArrayBuffer[Int]() / new ArrayBuffer[Int]
  5. b += 1
  6. b += (1,3,4)
  7. b ++= Array(4,5,6)
  8. b.trimEnd(5)
  9. b.insert(2, x)/insert(2, x,y,z)
  10. b.remove(2)/remove(2,3)
  11. b.toArray / a.toBuffer

提供初始值时不要使用new
用()来访问元素
用for (elem <- arr) 来遍历数组

  1. (0 until (a.length,2)).reverse

用 for (elem <- arr if ..) yield ...来将数组转变为新数组
集合类型与原集合相同,Array to Array,Buffer to Buffer
可以使用 .filter().map() 替换

  1. var first = true
  2. val indexes = for (i <- 0 until array.length if first || array(i)>=0) yield {if (array(i)<0) first=false;i}
  3. for (i <- 0 until indexes.length) {a(i)=a(indexs(i))}
  4. a.trimEnd(a.length - indexs.length)

常见函数
sum /max /min /b.sorted(_ < _)(返回排序后的)
scala.util.Sorting.quickSort(a) (不能使用在ArrayBuffer)
a.mkString( str )/ .mkString(a,b,c)

多维数组:数组的数组来实现
val m = Array.ofDimInt
m(3)(4) = 7
val t = new ArrayArray[Int]
for (i <- 0 to 9){t(i)=new ArrayInt}

Scala数组和Java数组可以互操作;用ArrayBuffer,使用scala.collection.JavaConversions中的转换函数

4 映射和元组

对偶是n=2的元组

创建、查询和遍历映射

  1. val m = Map('a'->3, 'b'->2, 'c'->4) //->构建对偶
  2. val m = Map(('a',3),('b',2), ('c',4))
  3. val m = scala.collection.mutable.Map(...)
  4. val m = new scala.collection.mutable.HashMap[String,Int]
  5. m('a')
  6. if (m.contains('a')) m('a') else 0
  7. m.getOrElse('a', 0)
  8. m.get('a') //返回一个Option对象,值或者None
  9. m += ('c'->4, 'e'->7)
  10. m -= 'a'
  11. val m3 = m1 + ('c'->3...) //新旧结构会共享
  12. for ((k,v) <- m) ....
  13. for (k <- m.keySet) ...
  14. for (v <- m.values) ...
  15. for ((k,v) <- m) yield (v,k)
  16. scala.collection.immutable.SortedMap //树Map
  17. scala.collection.mutable.LinkedHashMap //按插入顺序处理

需要从可变和不可变的映射中作出选择

默认情况下获得哈希映射,可以指明要树形映射

可以很容易在scala与java映射之间切换

  1. import scala.collection.JavaConversions.mapAsScalaMap

元组可以用来聚集值
元组是通过将单个的值包含在圆括号中构成的,可用于返回不止一个值的情况

  1. val t = new Tuple3[Int,Int,String]
  2. t._1, t._2, t._3
  3. val (first, sencond, _) = t//匹配模式获得值

zip操作
zip将Array合并成元组的Array

keys.zip(values).toMap

5 类

所有类都具有公有可见性

带getter和setter的属性
getter和setter分别叫做 x 和 x_=,可以自己重定义

  1. def age = privateAge
  2. def age_=(age: Int) {privateAge = age}

统一访问原则:某个模块提供的所有服务都应该是通过统一的表示法访问到,不管其是存储还是通过计算实现的

方法可以访问该类的所有对象的私有变量,包括其他实例
使用 private[this] ... 则只能访问本实例this的字段

将字段标注为 @BeanProperty 可生成Java的默认访问方法getXX, setXX

  1. import scala.reflect.BeanProperty
  2. class Person{
  3. @BeanProperty var name: String = _
  4. }

辅助构造器的名字为 this;每个辅助构造器都必须以一个先前已经定义的其他辅助构造器或主构造器的调用开始。没有显式定义主构造器则自动拥有一个无参数的主构造器

  1. def this(name: String){
  2. this()
  3. this.name = name
  4. }

主构造器:与类定义交织在一起,参数直接放在类名之后。参数被编译成字段,其值被初始化成构造时传入的参数。主构造器会执行类定义中的所有语句

  1. class Person(val name:String,private val age:Int){
  2. ...
  3. }

不带val或var的参数如果在类中至少被一个方法使用,则其升级为字段,相当于private[this] val。否则就仅是一个普通参数,供主构造器中代码访问。

可以在任何语法结构中内嵌任何语法结构。类中定义类。
每个实例都有它自己的内嵌类(泛型导致,实例有可能是不一样类型,其内嵌类也有可能不一样),进行操作时需要注意。需要公用,则可将其移入伴生对象中。或者使用类型投影。 外部类.this 访问外部类的 this 引用。

  1. class NetWork(val name:Int){ outer => //指向 NetWork.this
  2. class Member(){
  3. ... // use outer.name
  4. }
  5. }

6 对象

用对象作为单例或存放工具方法/常量或高效共享不可变实例
使用object创建单例,对象的构造器在其首次调用时指向。如果从未被使用,则其构造器不会被执行。其本质上可拥有类的所有特性,除了不能提供构造器参数。

类可以拥有一个同名的伴生对象
类与其伴生对象可以相互访问私有特性,必须存在同一个源文件中。

对象可以扩展类或者特质
其结果是一个扩展了指定类以及特质的类的对象,同时拥有在对象定义中给出的所有特性。

对象的apply方法通常用来构造伴生类的新实例
通常会定义和使用对象的apply方法,遇到如下形式的表达式,apply方法会被调用。返回的是伴生类的对象。可以省略使用 new

  1. object(para1, ... paran)

如果不想显式定义main方法,可以扩展App特质的对象
执行程序必须从某个对象的main方法开始

  1. object Hello{
  2. def main(args:Array[String]){
  3. ...
  4. }
  5. }
  6. object Hello extends App{
  7. if (args.length ...)...
  8. }

可以通过扩展Enumeration对象来实现枚举

  1. object TrafficLightColor extends Enumeration{
  2. val Red, Yellow, ... = Value
  3. val Red = Value(0, "stop")
  4. val Red = Value(10) //类型为TrafficLightColor.Value
  5. val Red = Value("Stop") //每次调用Value都返回新实例
  6. type TrafficLightColor = Value //类型别名
  7. }
  8. import TrafficLightColor._
  9. for (c <- TrafficLightColor.values) ...
  10. TrafficLightColor(0)
  11. TrafficLightColor.withName("Stop")

7 包和引入

包可以像内部类那样嵌套
源文件的目标与包之间没有强制的关联关系。可以在一个文档中定义多个包中的内容。

包路径不是绝对路径
包支持嵌套,可以访问上层作用域中的名称。如果需要访问非直接嵌套域中的包,则可以使用绝对包名 root. ....

包声明链x.y.z并不自动将中间包x和x.y变成可见

  1. package com.horstman.xx.xx{
  2. // com._, horstman_, xx_ 是不可见的
  3. }

位于文件顶部不带花括号的包声明在整个文件范围内有效

  1. package x.y.z
  2. package m
  3. //相当于
  4. package x.y.z{
  5. package m{
  6. ...
  7. }
  8. }

包对象可以持有函数和变量
包可以包含类、对象和特质,但不能包含函数或变量的定义。Java虚拟机的限制。每个包可以有一个包对象,需要在父包中定义,且名字与子包一样

  1. package x.y.x
  2. package object m{
  3. val xname =...
  4. } //Java虚拟机将其编译成带有静态方法和字段的JVM类,名为package.class
  5. package m{
  6. class P{
  7. var name = xname
  8. }
  9. }

可以通过private[packageName]来设置可见性

引入语句的目的是使用更短的名称而不是较长的名称
引入语句可以引入包、类和对象
引入语句可以出现在任何位置,将其放入需要的地方,可以减少可能的名称冲突

引入语句可以重命名和隐藏特定成员
选取器 .{A, B}
重命名 .{A=>Z, C}
隐藏 .{A=>_, _}

java.lang、scala、Predef总是被引入,每个文件默认以以下代码开头

  1. import java.lang._
  2. import scala._ //可覆盖前面一个,与用户引入不同
  3. import Predef._

8 继承

8.1 扩展类

使用extends
可以将类声明为final,这样它就不会被扩展

8.2 重写方法

重写非抽象方法必须使用override修饰符

调用超类方法与Java一样,使用super

8.3 类型检查和转换

使用isInstanceOf方法测试类型

  1. if (p.isInstanceOf[Boss]) {
  2. val s = p.asInstanceOf[Boss]
  3. }

判断一个对象只是某类而非子类

  1. if (p.getClass == classOf[Boss])

使用模式匹配

  1. p match{
  2. case s:Boss =>
  3. case _ =>
  4. }

8.4 受保护字段和方法

将字段或则方法声明为protected,成员可以被任何子类访问
还提供了protected[this],访问权限制在当前对象

8.5 超类的构造

只有主构造器才可以调用超类的构造器
class A(name:string, age:Int) extends B(name, age)

8.6 重写字段

可以用同名的val字段重写一个val(或不带参数的def)

8.7 匿名子类

  1. var alien = new Person("Fred"){
  2. def greeting = "....."
  3. }
  4. //创建一个结构类型的对象,新类型为 Person{def greeting:String}

8.8 抽象类

省区方法体的方法,抽象方法
只要存在一个抽象方法,就必须声明为abstract
重写超类的抽象方法时,不需要使用override方法

8.9 抽象字段

抽象字段是指没有初始值的字段

  1. var id: Int //生成了抽象的getter/setter方法,不带字段
  2. class B(var id:Int) extends AbstractClass {...}

8.10 构造顺序和提前定义

子类重写超类构造时所需的变量,则可以出现异常。

构造器中不应该依赖val的值

  1. class Ant extends{override val range=2} with Creature

C++中构造函数虚表先是基类然后才是子类,因此无法改变基类行为,Java允许在超类的构造方法中调用子类的方法。

8.11 继承层级

Any AnyVal Unit/Int/Float...
Any AnyRef ....
Null 类型唯一实例 null
Nothing 类型无实例, Nil的类型是List[Nothing]

8.12 对象相等性

AnyRef的eq方法比较两个引用是否指向同一个对象,equals调用eq。确保方法参数为Any。

9 文件与正则表达式

9.1 读取行

  1. source = scala.io.Source.fromFile(...)
  2. iters = source.getLines //迭代器
  3. iters.toArray/ toBuffer
  4. source.mkString

9.2 读取字符

  1. for (c <- source)
  2. iter = source.buffered
  3. while (iter.hasNext){
  4. if (iter.head)... iter.next()
  5. }

9.3 读取词法单元和数字

  1. val tokens = source.mkString.split("\\S+")
  2. val numbers = for (w <- tokens) yield w.toDoubel
  3. val numbers = tokens.map(_.toDouble)
  4. ReadInt

9.4 从URL或其他源读取

  1. Source.fromURL(....)
  2. Source.fromString(....)
  3. Source.stdin

9.5 读取二进制文件

通过Java库读取

  1. val file = new File(filename)
  2. val in = new FileInputStream(file)
  3. val bytes = new Array[Byte](file.length.toInt)
  4. in.read(bytes)
  5. in.close()

9.6 写入文本文件

  1. val out = new PrintWriter(...)
  2. for (i <- 1 to 100) out.println(i)
  3. out.close()

使用printf需要将其转为AnyRef,可以使用string的format方法

9.7 访问目录

  1. import java.io.File
  2. def subdirs(dir: File):Iterator[File] = {
  3. val children = dir.listFiles.filter(_.isDirectory)
  4. children.toInterator ++ children.toInterator.flagmap(subdirs _)
  5. }

9.8 序列化

9.9 进程控制

  1. import sys.process._
  2. "ls -al .."!

sus.process包含一个从字符串到ProcessBuilder对象的隐式转换,!就是执行的该对象。!!使用字符串的形式返回,#|作为管道。#>重定向 #>> #<

9.10 正则表达式

使用字符串.r方法构造Regex类对象
.findAllIn方法返回所有匹配项的迭代器,可以在for循环中使用
.findAllIn.tpArray
.findFirstIn
.findPrefixOf
.replaceFirstIn
.replaceAllIn

9.11 正则表达式组

  1. val pattern = "(..) (..)".r
  2. pattern(x, b) = "...."
  3. for (pattern(x,b) <- pattern.findAllIn(....)

10 特质

10.1 为什么没有多重继承

多基类中共同的元素
虚拟基类

10.2 当做接口使用的特质

特质中未被实现的方法默认就是抽象的,需要特质不止一个,可以使用with关键字来添加额外的特质

  1. trait Logger{
  2. def log(msg:String) //抽象方法
  3. }

10.3 带有具体实现的特质

特质中的方法也可以是具体的
如果特质中有具体的方法,当特质发生改变时,所有混入该特质的类都必须重写编译

10.4 带有特质的对象

可以在类定义时添加特质
而在对象中混入具体实现

  1. class X extends Y with Logged{...}
  2. val x = new X with BetterLogged

10.5 叠加在一起的特质

可以为类或对象添加多个互相调用的特质,从最后一个开始

10.6 在特质中重写抽象方法

写的时候往往会调用父类的同名函数,因为不知道实际调用时的次序,如果父类是抽象方法,则编译会出错

  1. abstract override def log(msg:String){
  2. super.log(new .....)
  3. }

10.7 当做富接口使用的特质

在特质中使用具体和抽象方法十分普遍

10.8 特质中的具体字段

特质中的字段可以是具体的也可以是抽象的,混入该特质的类自动获得一个字段与之对应。该字段不是继承,只是简单加入子类中。由于Java限定; 超类只有一个,因此特质的字段,就相当于处于子类的。

10.9 特质中的抽象字段必须重写

1)可以将抽象字段定义到类中
2)也可以将其定义到具体的对象中
不需要写 override

10.10 特质构造顺序

构造器顺序

构造器的顺序是类的线性化的反向

10.11 初始化特质中的字段

特质不能有构造器参数。
采用抽象字段可能会有问题,如果构造器中需要使用该字段的话,其初始化在子类。

  1. val x = new {val filename = ...} with Account with FileLogger
  2. class X extends {val filename = ...} with Account with FileLogger{}

10.12 扩展类的特质

特质也可以扩展类,该类会自动成为所有混入该特质的超类。同时如果其中有多个超类,如特质的超类和子类的超类是超类关系,则没有问题。

10.13 自身类型

当特质代码以如下代码开头定时,它就只能被混入指定类型的子类

  1. trait ... {this: 类型 => ...}

自身类型也可以处理结构类型,这种结构只给出类必须拥有的方法,而不是类的名称

  1. trait xx extends Logged{
  2. this: {def getmessage():String} =>
  3. ...
  4. }

10.14 背后发生了什么

11 操作符

11.1 标识符

变量、函数、类等的名称统称为标识符
可以使用很多字符
反引号中包含几乎任何字符序列

11.2 中置操作符

a 标识符 b, 标识符代表一个带有两个参数的方法(一个隐式参数和一个显式参数)。

11.3 一元操作符

a 操作符,为后置操作符
+-!~可以作为前置(prefix)操作符,被转换为 unary_操作符的方法调用

11.4 赋值操作符

a 操作符= b / a = a 操作符 b

11.5 优先级

11.6 结合性

所有操作符都是左结合的,除了以:结尾的操作符和赋值操作符

1::2::Nil 是右结合的,用于构造列表
右结合的二元操作符是其第二个参数的方法
2::Nil === Nil.::(2)

11.7 apply和update方法

如果f不是函数和方法则可将其扩展到apply方法上
f(arg1, arg2 ..) ---> f.apply(arg1, arg2 ..)
如果其出现在赋值语句的等号左边,则有如下,用于数组与映射
f(arg1...) = value ---> f.update(arg1 ... ,value)
apply方法同样被经常用在伴生对象中,用来构造对象而不用显式地使用new
object F(){def apply(n:Int) = new F(n)}

11.8 提取器

提取器是带有unapply方法的对象,可以将unapply方法当做是伴生对象中apply方法的反向操作。接受对象,并从中提取值,而这些值是当初用来构建该对象的值
val F(x, y) = ... // F.unapply(...)
case Currency(amount, "USD") => ...

11.9 带单个参数或无参数的提取器

没有单值,只能放回一个目标类型的Option
def unapply(intput:string):Option[Int]=...

object IsCompound{
def unapply(input:String) = input.contain("....")
}
author match{
case Name(first, last @ IsCompound()) =>
case Name(first, last) =>
}

11.10 unapplySeq方法

def unapplySeq(input:String):Option[Seq[String]]

author match{
case Name(first,last) =>
case Name(f,m,l)=>
case Name(f,'xx','yy',l)=>
}

12 高阶函数

12.1 作为值的函数

import scala.math._
val fun = ceil _ // _表示确实指的杉树,而不是碰巧忘记给参数

可以调用
可以传递,fun是一个包含函数的变量

12.2 匿名函数

val trip = (x:Double)=>3*x
与 def trip(x:Double) = 3*x 一样

12.3 带函数参数的函数

def valueAtOne(f:(Double)=>Double)) = f(2.5) //((Double)=>Double)=>Double
valueAtOne(ceil _) / valueAtOne(sqrt _)

12.4 参数(类型)推断

valueAtOne((x)=>3*x)
valueAtOne(x=>3*x)
valueAtOne(3*_)

val fun = 3*(: Double)
val fun:(Double)=>Double = 3*

12.5 一些有用的高阶函数

foreach/map/filter/reduceLeft/sortWith

12.6 闭包

12.7 SAM转换

12.8 柯里化 currying

将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数作为参数的函数
scala支持简写来定义柯里化函数
def mulOneAtTime(x:Int)(y:Int) = x*y

12.9 控制抽象

  1. def runInThread(block:()=>Unit){
  2. new Thread{
  3. override def run(){block()}
  4. }.start()
  5. }
  6. runInThread{()=> println....}
  7. def runInThread(block:=>Unit){ //换名调用表示法
  8. new Thread{
  9. override def run(){block}
  10. }.start()
  11. }
  12. runInThread{ println....}
  13. def until(condition:=>Blooean)(block:=>Unit){
  14. if (!condition){
  15. block
  16. until(condition)(block)
  17. }
  18. }
  19. var x = 10
  20. until (x == 0){
  21. x -= 1
  22. ...
  23. }

12.10 return表达式

不需要使用return语句来返回函数值,函数的返回值就是函数体的值
如果需要在带名函数中使用return的化,则必须给出其返回类型

  1. def indexOf(str:String, ch:Char):Int = {
  2. var i = 0
  3. until (i == str.length){
  4. if (str(i) == ch) return i
  5. i += 1
  6. }
  7. return -1
  8. }

13 集合

13.1 主要集合特质

val iter = coll.iterator
while (iter.hasNext){
dowith(iter.next())
}

13.2 可变与不可变集合

13.3 序列

Vector是ArrayBuffer的不可变版本

13.4 List

13.5 可变列表

LinkedList

13.6 集合

13.7 流

21 隐式转换和隐式参数

21.1 隐式转换

是指哪种以implicit关键字声明的单个参数的函数,将被自动应用,将值从一种类型转换为另一种类型,最好采用长命名 oldtype2newtype

21.2 利用隐式转换丰富现有类库的功能

21.3 引入隐式转换

21.4 隐式转换规则

以下情况不会尝试考虑隐式转换

21.5 隐式参数

如果在参数列表中设置了隐式参数,则当调用时不传递该参数,则会自动寻找为该类型的隐式值

  1. def x(implicit delims:String, x:String)

21.6 利用隐式参数进行隐式转换

order是隐式参数,省略情况下可以直接比较

  1. def smaller[T](a:T,b:T)(implicit order: T => Order[T]) = if (order(a)<b) a else b

order是隐式函数,且单个参数,单标识符出现,则其可以用于隐式转换,因此函数调用中可以去掉

  1. def smaller[T](a:T,b:T)(implicit order: T => Order[T]) = if (a<b) a else b

21.7 上下文界定

类型参数可以有一个形式为T:M的上下文界定,其中M为泛函类型,要求作用域中存在一个类型为M[T]的隐式值,可以使用在该类中

  1. class Pair[T:Ording](val first:T,val second:T){
  2. def smaller(implicit ord:Ording[T]) = {
  3. if (ord.compare(first, second)<0) first else second
  4. }
  5. }
  6. // 可以通过Predef中的implicitly获取该值,代替上面的ord
  7. // implicitly[Ording[T]]
  8. // 定义如下 def implicitly[t](implicit e:T) = e
  9. class Pair[T:Ording](val first:T,val second:T){
  10. def smaller = {
  11. import Ordered._
  12. if (first < second) first else second
  13. }
  14. }
  15. implicit object PonitOrdering extends Ordering[Point]{
  16. def compare(...)
  17. }

21.8 类型证明

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注