Scala 基本实体

Expression

表达式:可计算的语句

  • value:常量,引用常量不会再次计算
    • 不能被重新赋值
    • 类型可以被推断、也可以显式声明类型
    • 可以被声明为lazy,只有被真正使用时才被赋值
1
2
3
val constant = 1
lazy val lazy_constant = 1
var variable = 1
  • variable:变量,除可重新赋值外类似常量

Unified Types

  • Scala中所有值都有类型,包括数值、函数

类型层次结构

scala_unified_types_diagram

  • Any:顶级类型,所有类型超类

    • 定义一些通用方法
      • equals
      • hashCode
      • toString
  • AnyVal:值类型

    • 有9个预定义非空值类型
      • Double
      • Float
      • Long
      • Int
      • Short
      • Byte
      • Char
      • Boolean
      • Unit:唯一单例值()
    • 值类型可以按以下方向转换(非父子类之间) scala_value_types_casting_diagram
  • AnyRef:引用类型

    • 所有非值类型都被定义为引用类型
    • 用户自定义类型都是其子类型
    • 若Scala被应用于Java运行环境中,AnyRef相当于 java.lang.object
  • Nothing:底部类型,所有类型的子类型

    • 没有值是Nothing类型
    • 可以视为
      • 不对值进行定义的表达式的类型
      • 不能正常返回的方法
    • 用途
      • 给出非正常终止的信号:抛出异常、程序退出、死循环
  • NULL:所有引用类型的子类型

    • 唯一单例值null
    • 用途
      • 使得Scala满足和其他JVM语言的互操作性,几乎不应该 在Scala代码中使用

基本数据类型

数据类型 取值范围 说明
Byte $[-2^7, 2^7-1]$
Short $[-2^{15}, 2^{15}-1]$
Int $[-2^{31}, 2^{31}-1]$
Long $[-2^{63}, 2^{63}]$
Char $[0, 2^{16}-1]$
String 连续字符串 按值比较
Float 32bits浮点
Double 64bits浮点
Boolean truefalse
Unit () 不带任何意义的值类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
val x = 0x2F		// 16进制
val x = 41 // 10进制
val x = 041 // 8进制

val f = 3.14F
val f = 3.14f // `Float`浮点
val d = 3.14
val d = 0.314e1 // `Double`

val c = 'A'
val c = '\u0022'
val c = '\"' // `Char类型`

val str = "\"hello, world\"" // 字符串
val str = """"hello, world"""" // 忽略转义

var x = true
  • Unit()在概念上与Tuple0()相同(均唯一单例值) (但Tuple0类型不存在???)

Tuples

元组:不同类型值的聚集,可将不同类型的值放在同一个变量中保存

  • 元组包含一系列类:Tuple2Tuple3直到Tuple22

    • 创建包含n个元素的元组时,就是从中实例化相应类,用 组成元素的类型进行参数化
  • 特点

    • 比较:按值(内容)比较
    • 输出:输出括号包括的内容
  • 访问元素

    • <tuple>._<n>:访问元组中第n个元素(从1开始)
    • 元组支持模式匹配[解构]
  • 用途

    • 需要从函数中返回多个值
1
2
3
4
5
6
7
8
val t3 = ("hello", 1, 3.14)
val (str, i, d) = t3
// 必须括号包裹,否则三个变量被赋同值
println(t3._1)

val t2 = (1, 3)
val t22 = 1 -> 3
// `->`同样可以用于创建元组
  • 基于内容的比较衍生出模式匹配提取
  • 如果元素具有更多含义选择case class,否则选择元组

Symbol

符号类型:起标识作用,在模式匹配、内容判断中常用

  • 特点
    • 比较:按内容比较
    • 输出:原样输出
1
2
val s: Symbol = 'start
if (s == 'start) println("start......")

运算符号

  • 任何具有单个参数的方法都可以用作中缀运算符,而运算符 (狭义)就是普通方法

    • 可以使用点号调用
    • 可以像普通方法一样定义运算符
      1
      2
      3
      4
      case class Vec(val x: Double, val y: Double){
      def +(that: Vec) =
      new Vec(this.x + that.x, this.y + that.y)
      }
  • 表达式中运算符优先级:根据运算符第一个字符评估优先级

    • 其他未列出字符
    • */%
    • +-
    • :
    • +!
    • <>
    • &
    • ^
    • |
    • 所有字母:[a-zA-Z]

数值运算

  • 四则运算

    • +
    • -
    • *
    • /
    • %
  • 按位

    • &|^~
    • >>/<<:有符号移位
    • >>>/<<<:无符号移位
  • 比较运算

  • ><<=>=
  • ==/equals:基于内容比较
  • eq:基于引用比较

  • 逻辑运算

    • ||&&

字符串运算

  • Scala中String实际就是Java中java.lang.String类型
    • 可调用Java中String所有方法
    • 并定义有将String转换为StingOps类型的隐式转换函数 ,可用某某些额外方法
  • indexOf(<idx>)
  • toUppercasetoLowercase
  • reverse
  • drop(<idx>)
  • slice<<start>,<end>>
  • .r:字符串转换为正则表达式对象

类型推断

  • 编译器通常可以推断出

    • 表达式类型:工作原理类似推断方法返回值类型
    • 非递归方法的返回值类型
    • 多态方法、泛型类中泛型参数类型
  • 编译器不推断方法的形参类型

    • 但某些情况下,编译器可以推断作为参数传递的匿名函数 形参类型
  • 不应该依赖类型推断场合

    • 公开可访问API成员应该具有显式类型声明以增加可读性
    • 避免类型推断推断类型太具体

      1
      2
      var obj = null
      // 被推断为为`Null`类型仅有单例值,不能再分配其他值

类型别名

类型别名:具体类型别名

1
2
// 泛型参数必须指定具体类型
type JHashMap = java.util.HashMap[String, String]

Code Blocks

代码块:使用{}包围的表达式组合

  • 代码块中最后表达式的结果作为整个代码块的结果
    • Scala建议使用纯函数,函数不应该有副作用
    • 所以其中基本应该没有语句概念,所有代码块均可视为 表达式,用于赋值语句
  • {}:允许换行的(),Scala中{}基本同()

控制表达式

if语句

1
2
3
4
5
6
7
if(<eva_expr>){
// code-block if true
}else if(<eva_expr>){
// code-block
}else{
// code-block if false
}
  • 返回值:对应函数块返回值

while语句

1
2
3
while(<eva_expr>){
// code-block if true
}
  • Scala中while作用被弱化、不推荐使用
  • 返回值:始终为Unit类型()

for语句

1
2
3
4
5
6
7
for{
<item1> <- <iter1>
<item2> <- <iter2>
if <filter_exp>
if <filter_exp>
}{
}
  • 以上在同语句中多个迭代表达式等价于嵌套for
  • 返回值
    • 默认返回Unit类型()
    • 配合yield返回值迭代器(元素会被消耗)
  • 注意:迭代器中元素会被消耗,大部分情况不应该直接在嵌套 for语句中使用

match模式匹配

  • 模式匹配的候选模式

    • 常量
    • 构造函数:解构对象
      • 需伴生对象实现有unapply方法,如:case class
    • 序列
      • 需要类伴生对象实现有unapplySeq方法,如: Seq[+A]类、及其子类
    • 元组
    • 类型:适合需要对不同类型对象需要调用不同方法
      • 一般使用类型首字母作为case标识符name
      • 对密封类,无需匹配其他任意情况的case
      • 不匹配可以隐式转换的类型
    • 变量绑定
  • 候选模式可以增加pattern guards以更灵活的控制程序

  • 模式匹配可以视为解构已有值,将解构结果map至给定名称

    • 可以用于普通赋值语句中用于解构模式
    • 显然也适合于for语句中模式匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<target> match {
// 常量模式 + 守卫语句
case x if x % 2 == 0 =>
// 构造函数模式
case Dog(a, b) =>
// 序列模式
case Array(_, second) =>
// 元组模式
case (second, _*) =>
// 类型模式
case str: String =>
// 变量绑定
case all@Dog(name, _) =>
}

// 普通赋值语句中模式匹配
val all@Array(head, _*) = Array(1,3,3)
  • 可在普通类伴生对象中实现unapplyunapplySeq实现模式 匹配,此单例对象称为extractor objects

unapply

unapply方法:接受实例对象、返回创建其所用的参数

  • 构造模式依靠类伴生对象中unapply实现
  • unapply方法返回值应该符合
    • 若用于判断真假,可以返回Boolean类型值
    • 若用于提取单个T类型的值,可以返回Option[T]
    • 若需要提取多个类型的值,可以将其放在可选元组 Option[(T1, T2, ..., Tn)]
  • case class默认实现有此方法
1
2
3
4
5
6
7
8
class Dog(val name: String, val age: Int){
// 伴生对象中定义`unapply`方法解构对象
object Dog{
def unapply(dog: Dog): Option[(String, Int)]{
if (dog!=null) Some(dog.name, dog.age)
else None
}
}

unapplySeq

unapplySeq方法:接受实例对象、返回创建其所用的序列

  • 序列模式依靠类伴生对象中unapplySeq实现
  • 方法应返回Option[Seq[T]]
  • Seq[A+]默认实现此方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// `scala.Array`伴生对象定义
object Array extends FallbackArrayBuilding{
def apply[T: ClassTag](xs: T*): Array[T] = {
val array = new Array[T](xs.length)
var i = 0
for(x <- xs.iterator) {
array(i) = x
i += 1
}
array
}
def apply[x: Boolean, xs: Boolean*]: Array[Boolean] = {
val array = new Array[Boolean](xs.length + 1)
array(0) = x
var i = 1
for(x <- xs.iterator){
array(i) = x
i += 1
}
array
}
/* 省略其它`apply`方法 */
def unapplySeq[T](x: Array[T]): Option[IndexedSeq[T]] =
if(x == null) None
else Some(x.toIndexedSeq)
}

正则表达式模式匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import scala.util.matching.{Regex, MatchIterator}
// `.r`方法将字符串转换为正则表达式
val dateP = """(\d\d\d\d)-(\d\d)-(\d\d)""".r
val dateP = new Regex("""(\d\d\d\d)-(\d\d)-(\d\d)""")

// 利用`Regex`的`unapplySeq`实现模式匹配
// 不过此应该是定义在`Regex`类而不是伴生对象中
for(date <- dateP.findAllIn("2015-12-31 2016-01-01")){
date match {
case dateRegex(year, month, day) =>
case _ =>
}
}
// `for`语句中模式匹配
for(dateP(year, month, day) <- dateP.findAllIn("2015-12-31 2016-01-01")){
}
  • scala.util.matching具体参见cs_java/scala/stdlib

Functions

函数:带有参数的表达式

1
2
3
4
5
// 带一个参数的匿名函数,一般用作高阶函数参数
Array(1,2,3).map((x: Int) => x + 1)
Array(1,2,3).map(_+1) // 简化写法
// 具名函数
val fx: Float => Int = (x: Float) => x.toInt
  • 函数结构

    • 可以带有多个参数、不带参数
    • 无法显式声明返回值类型
  • 函数可以类似普通常量使用lazy修饰,当函数被使用时才会被 创建

  • 作高阶函数参数时可简化

    • 参数类型可推断、可被省略
    • 仅有一个参数,可省略参数周围括号
    • 仅有一个参数,可使用占位符_替代参数声明整体
  • Java:函数被编译为内部类,使用时被创建为对象、赋值给相应 变量
  • Scala中函数是“一等公民”,允许定义高阶函数、方法
    • 可以传入对象方法作为高阶函数的参数,Scala编译器会将 方法强制转换为函数
    • 使用高阶函数利于减少冗余代码

函数类型

函数类型:Scala中有Funtion<N>[T1, T2, ..., TN+1]共22种函数 类型,最后泛型参数为返回值类型

1
2
3
4
5
// 实例化`Function2[T1, T2, T3]`创建函数
val sum = new Function2[Int, Int, Int] {
def apply(x: Int, y: Int): Int = x + y
}
val sum = (x: Int, y: Int) => x + y

偏函数

偏函数:只处理参数定义域中子集,子集之外参数抛出异常

1
2
3
4
5
6
7
8
9
10
11
// scala中定义
trait PartialFunction[-A, +B] extends (A => B){
// 判断元素在偏函数处理范围内
def isDefinedAt(?ele: A)
// 组合多个偏函数
def orElse(?pf: PartialFunction[A, B])
// 方法的连续调用
def addThen(?pf: PartialFunction[A, B])
// 匹配则调用、否则调用回调函数
def applyOrElse(?ele: A, ?callback: Function1[B, Any])
}
  • 偏函数实现了Function1特质
  • 用途
    • 适合作为map函数参数,利用模式匹配简化代码
1
2
3
4
5
val receive: PartialFunction[Any, Unit] = {
case x: Int => println("Int type")
case x: String => println("String type")
case _ => println("other type")
}

Methods

方法:表现、行为类似函数,但有关键差别

  • def定义方法,包括方法名、参数列表、返回类型、方法体

  • 方法可以接受多个参数列表、没有参数列表

    1
    2
    3
    def addThenMutltiply(x: Int, y: Int)(multiplier: Int): Int = (x+y) * multiplier

    def name: String = System.getProperty("user.name")
  • Scala中可以嵌套定义方法

  • Java中全在类内,确实都是方法

Currying

柯里化:使用较少的参数列表调用多参数列表方法时会产生新函数, 该函数接受剩余参数列表作为其参数

  • 多参数列表/参数分段有更复杂的调用语法,适用场景
    • 给定部分参数列表
      • 可以尽可能利用类型推断,简化代码
      • 创建新函数,复用代码
    • 指定参数列表中部分参数为implicit
1
2
3
4
5
6
7
8
val number = List(1,2,3,4,5,6,7,8,9)
numbers.foldLeft(0)(_ + _)
// 柯里化生成新函数
val numberFunc = numbers.foldLeft(List[Int]())_
val square = numberFunc((xs, x) => xs:+ x*x)
val cube = numberFunc((xs, x) => xs:+ x*x*x)

def execute(arg: Int)(implicit ec: ExecutionContext)

隐式转换

隐式转换:编译器发现类型不匹配时,在作用域范围内查找能够转换 类型的隐式转换

  • 类型S到类型T的隐式转换由S => T类型函数的隐式值、 或可以转换为所需值的隐式方法定义

    • 隐式转换只与源类型、目标类型有关
    • 源类型到目标类型的隐式转换只会进行一次
    • 若作用域中有多个隐式转换,编译器将报错
  • 适用场合:以下情况将搜索隐式转换

    • 隐式转换函数、类:表达式e的类型S不符合期望类型 T
    • 隐式参数列表、值:表达式e类型S没有声明被访问的 成员m
  • 隐式转换可能会导致陷阱,编译器会在编译隐式转换定义时发出 警告,可以如下关闭警告

    • import scala.language.implicitConversions到隐式 转换上下文范围内
    • 启用编译器选项-language: implicitConversions
1
2
3
4
5
6
7
8
import scala.language.implicitCoversions

// `scala.Predef`中定义有
// 隐式转换函数
implicit def int2Integer(x: Int) =
java.lang.Integer.valueOf(x)
// 隐式参数列表
@inline def implicitly[T](implicit e:T) = e

隐式转换函数、类

1
2
3
4
5
6
7
8
9
10
11
// 隐式转换函数
implicit def float2int(x: Float) = x.toInt

// 隐式类
implicit class Dog(val name: String){
def bark = println(s"$name is barking")
}
"Tom".bark

// 隐式类最终被翻译为
implicit def string2Dog(name: String): Dog = new Dog(name)
  • 隐式类通过隐式转换函数实现
    • 其主构造函数参数有且只有一个
    • 代码更简洁、晦涩,类和方法绑定

隐式值、参数列表

  • 若参数列表中参数没正常传递,Scala将尝试自动传递 正确类型的隐式值

  • 查找隐式参数的位置

    • 调用包含隐式参数列表的方法时,首先查找可以直接访问、 无前缀的隐式定义、隐式参数
    • 在伴生对象中查找与隐式候选类型相关的、有隐式标记的 成员
  • 说明

    • 隐式值不能是顶级值
    • implicit能且只能作用于最后参数列表
    • 方法才能包含隐式参数列表,函数不可
    • 包含隐式参数列表方法不可偏函数化
  • 例1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    abstract class Monoid[A]{
    def add(x: A, y: A): A
    def unit: A
    }
    object ImplicitTest{
    // `implicit`声明该匿名类对象为隐式值
    implicit val stringMonoid: Monoid[String] = new Monoid[String]{
    def add(x: String, y: String): Strinng = x concat y
    def unit: String = ""
    }
    implicit val intMonoid: Monoid[Int] = new Monoid[Int] {
    def add(x: Int, y: Int): Int = x + y
    def unit: Int = 0
    }

    // 定义隐式参数列表
    def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
    if (xs.isEmpty) m.unit
    else m.add(xs.head, sum(xs.tail))

    def main(args: Array[String]): Unit = {
    println(sum(List(1,2,3)))
    println(sum(List("a", "b", "c")))
    }
    }
  • 例2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    trait Multiplicable[T] {
    def multiply(x: T): T
    }
    // 定义隐式单例对象
    implicit object MultiplicableInt extends Multiplicable[Int]{
    def multiply(x: Int) = x*x
    }
    implicit object MultiplicableString extends Mulitplicable[String]{
    def multiply(x: String) = x*2
    }
    // `T: Multiplicable`限定作用域存在相应隐式对象、或隐式值
    def multiply[T: Multiplicable](x: T) = {
    // 调用`implicitly`返回隐式对象、或隐式值
    val ev = implicitly[Multiplcable[T]]
    ev.multiply(x)
    }
    println(multiply(5))
    println(multiply(5))

传名参数

传名参数:仅在被使用时触发实际参数的求值运算

1
2
def calculate(input: => Int) = input * 37
// 在参数类型前加上`=>`将普通(传值)参数变为传名参数
  • 传名参数
    • 若在函数体中未被使用,则不会对其求值
    • 若参数是计算密集、长时运算的代码块,延迟计算能力可以 帮助提高性能
  • 传值参数:仅被计算一次

传名参数实现while循环

1
2
3
4
5
6
7
8
9
10
11
def whileLoop(condition: => Boolean)(body: => Unit): Unit =
if(condition){
body
whileLoop(condition)(body)
}

var i = 2
whileLoop(i > 0){
println(i)
i -= 1
}

默认参数

默认参数:调用时可以忽略具有默认值的参数

  • 调用时忽略前置参数时,其他参数必须带名传递
  • 跳过前置可选参数传参必须带名传参
  • Java中可以通过剔除可选参数的重载方法实现同样效果
  • Java代码中调用时,Scala中默认参数必须、不能使用命名参数

命名参数调用

  • 调用方法时,实际参数可以通过其对应形参名标记
    • 命名参数顺序可以打乱
    • 未命名参数需要按照方法签名中形参顺序放在前面

Exception

异常处理:try{throw...}catch{case...}抛出捕获异常

1
2
3
4
5
6
7
8
9
10
def main(args: Array[String]){
try{
val fp = new FileReader("Input")
}catch{
case ex: FileNotFoundException => println("File Missing")
case ex: IOException => println("IO Exception")
}finally{
println("finally")
}
}

Scala 自定义实体

Class

  • Scala是纯面向对象语言,所有变量都是对象,所有操作都是 方法调用
  • class定义类,包括类名、主构造参数,其中成员可能包括

    • 普通类可用new创建类实例

成员说明

  • 常/变量:声明时必须初始化,否则需定义为抽象类, 相应成员也被称为抽象成员常/变量
    • 可以使用占位符_初始化
      • AnyVal子类:0
      • AnyRef子类:null
  • 抽象类型:type声明
  • 方法、函数
  • 内部类、单例对象:内部类、单例对象绑定至外部对象
    • 内部类是路径依赖类型
    • 不同类实例中的单例对象不同
  • Java:仍然会为匿名类生成相应字节码文件
  • Java:public类成员常/变量在编译后自动生成getter、 setter(仅变量)方法
    • 即使是公有成员也会被编译为private
    • 对其访问实际上是通过setter、getter方法(可显式调用)

内部类型投影

类型投影:Outter#Inner类型InnerOutter类型作为前缀, Outter不同实例中Inner均为此类型子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Outter{
class Inner
def test(i: Outter#Inner) = i
}

val o1 = new Outter
val o2 = new Outter

// 二者为不同类型
val i1 = new o1.Inner
val i2 = new o2.Inner

// 类型投影父类作为参数类型
o1.test(i2)
o2.test(i1)

单例类型

单例类型:所有对象都对应的类型,任意对象对应单例类型不同

1
2
3
4
5
6
import scla.reflect.runtime.universe.typeOf
class Dog
val d1 = new Dog
val d2 = new Dog
typeOf[d1.type] == typeOf[d2.type] // `false`
val d3: d1.type = d1 // 两者单例类型也不同
链式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Pet{
var name: String = _
var age: Float = _
// 返回类型单例类型,保证类型不改变
def setName(name: String): this.type = {
this.name = name
this
}
def setAge(age: Float): this.age = {
this.age = age
this
}
}
class Dog extends Pet{
var weight: Float = _
def setWeight(weight: Float): this.type = {
this.weight = weight
this
}
}

// 若没有设置返回类型为单例类型`this.type`,`setAge`之后
// 将返回`Pet`类型,没有`setWeight`方法,报错
val d = new Dog().setAge(2).setWeight(2.3).setName("Tom")

Constructor

1
2
3
4
5
6
7
8
// 带默认参数的、私有主构造方法
class Person private(var name: String, var age: Int=10){
// 辅助构造方法
def this(age: Int) = {
// 必须调用已定义构造方法
this("", age)
}
}
  • 主构造方法:类签名中参数列表、类体

    • 其参数(类签名中)被自动注册为类成员变、常量

      • 其可变性同样由varval声明
      • 其访问控制类似类似类体中成员访问控制
      • 但缺省继承父类可见性,否则为private[this] val
      • 参数中私有成员变、常量没有被使用,则不会被生成
      • case class主构造方法中缺省为public val
    • 其函数体为整个类体,创建对象时被执行

    • 可类似普通方法

      • 提供默认值来拥有可选参数
      • 将主构造方法声明为私有
  • 辅助构造方法:this方法

    • 辅助构造方法必须以先前已定义的其他辅助构造方法、或 主构造方法的调用开始
  • 主构造方法体现于其中参数被自动注册为类成员

类继承

1
2
3
4
// `extends`指定类继承
class Student(name: String, age: Int, var studentNo: Int)
extends Person(name: String, age: Int){
}
  • 必须给出父类的构造方法(可以是辅助构造方法)

    • 类似强制性C++中初始化列表
  • 构造函数按以下规则、顺序执行

    • 父类、特质构造函数
    • 混入特质:多个特质按从左至右
    • 当前类构造函数
  • 可用override重写从父类继承的成员方法、常/变量

    • 重写父类抽象成员(未初始化)可省略override
    • java中方法都可以视为C++中虚函数,具有多态特性

提前定义、懒加载

  • 由于构造方法的执行顺序问题,定义于trait中的抽象成员 常/变量可能会在初始化化之前被使用
  • 可以考虑使用提前定义、懒加载避免
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.PrintWriter
trait FileLogger{
// 抽象变量
val fileName: String
// 抽象变量被使用
val fileOutput = new PrintWriter(fileName: String)
// `lazy`懒加载,延迟变量创建
lazy val fileOutput = new PrintWriter(fileName: String)
}
class Person
class Student extends Person with FileLogger{
val fileName = "file.log"
}
val s = new {
// 提前定义
override val fileName = "file.log"
} with Student

访问控制

  • 缺省:Scala没有public关键字,缺省即为public
  • protected[<X>]:在X范围内的类、子类中可见
  • private[<X>]:在X范围内类可见
  • X可为包、类、单例对象等作用域

    • 缺省为包含当前实体的上层包、类、单例对象,即类似Java 中对应关键字
  • 特别的private[this]表示对象私有

    • 只有对象本身能够访问该成员
    • 类、伴生对象中也不能访问

      1
      2
      3
      4
      5
      6
      7
      8
      class Person{
      private[this] age = 12
      def visit() = {
      val p = new Person
      // 报错,`age`不是`Person`成员
      println(p.age)
      }
      }

特殊类

abstract class

抽象类:不能被实例化的类

  • 抽象类中可存在抽象成员常/变量、方法,需在子类中被具体化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义抽象类
abstract class Person(var name: String, var age: Int){
// 抽象成员常量
val gender: String
// 抽象方法
def print: Unit
}

// 抽象类、泛型、抽象类型常用于匿名类
val p = new Person("Tom", 8){
override gender = "male"
// 覆盖抽象成员可以省略`override`
def print: Unit = println("hello")
}

case class

样例类

  • 和普通类相比具有以下默认实现

    • apply:工厂方法负责对象创建,无需new实例化样例类
    • unapply:解构方法负责对象解构,方便模式匹配
    • equals:案例类按值比较(而不是按引用比较)
    • toString
    • hashCode
    • copy:创建案例类实例的浅拷贝,可以指定部分构造参数 修改部分值
  • 类主构造函数参数缺省为val,即参数缺省不可变公开

  • 用途

    • 方便模式匹配
  • 样例类一般实例化为不可变对象,不推荐实例化为可变
1
2
3
4
case class Message(sender: String, recipient: String, body: String)
val message4 = Message("A", "B", "message")
// 指定部分构造参数的拷贝
val message5 = message.copy(sender=message4.recipient, recipient="C")

Value Class

Value Class:通过继承AnyVal以避免运行时对象分配机制

1
class Wrapper(val underlying: Int) extends AnyVal
  • 特点

    • 参数:仅有被用作运行时底层表示的公有val参数
    • 成员:可包含def自定义方法,但不能定义额外valvar、内嵌traitclassobject
    • 继承:只能继承universal trait,自身不能再被继承
    • 编译期类型为自定义类型,运行时类型为val参数类型
    • 调用universal trait中方法会带来对象分配开销
  • 用途

    • 和隐式类联合获得免分配扩展方法

      1
      2
      3
      4
      // 标准库中`RichInt`定义
      implicit class RichInt(val self Int) extends AnyVal{
      def toHexString: String = java.lang.Integer.toHexString(self)
      }
    • 不增加运行时开销同时保证数据类型安全

      1
      2
      3
      class Meter(val value: Double) extends AnyVal{
      def +(m: Meter): Meter = new Meter(value + m.value)
      }
  • JVM不支持value class,Scala有时需实例化value class
    • value class作为其他类型使用
    • value class被赋值给数组
    • 执行运行时类型测试,如:模式匹配

Object

单例对象:有且只有一个实例的特殊类,其中方法可以直接访问, 无需实例化

  • 单例对象由自身定义,可以视为其自身类的单例

    • 对象定义在顶层(没有包含在其他类中时),单例对象只有 一个实例
      • 全局唯一
      • 具有稳定路径,可以被import语句导入
    • 对象定义在类、方法中时,单例对象表现类似惰性变量
      • 不同实例中单例对象不同,依赖于包装其的实例
      • 单例对象和普通类成员同样是路径相关的
  • 单例对象是延迟创建的,在第一次使用时被创建

    • object定义
    • 可以通过引用其名称访问对象
  • 单例对象被编译为单例模式、静态类成员实现

Companion Object/Class

  • 伴生对象:和某个类共享名称的单例对象
  • 伴生类:和某个单例对象共享名称的类
  • 伴生类和伴生对象之间可以互相访问私有成员
    • 伴生类、伴生对象必须定义在同一个源文件中
  • 用途:在伴生对象中定义在伴生类中不依赖于实例化对象而存在 的成员变量、方法
    • 工厂方法
    • 公共变量
  • Java中static成员对应于伴生对象的普通成员
  • 静态转发:在Java中调用伴生对象时,其中成员被定义为伴生类 中static成员(当没有定义伴生类时)

apply

apply方法:像构造器接受参数、创建实例对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import scala.util.Random

object CustomerID{
def apply(name: String) = s"$name--${Random.nextLong}"
def unapply(customerID: String): Option[String] = {
val stringArray:[String] = customer.ID.split("--")
if (stringArray.tail.nonEmpty) Some(StringArray.head)
else None
}
}

val customer1ID = CustomerId("Tom")
customer1ID match {
case CustomerID(name) => println(name)
case _ => println("could not extract a CustomerID")
}
val CustomerID(name) = customer1ID
// 变量定义中可以使用模式引入变量名
// 此处即使用提取器初始化变量,使用`unapply`方法生成值
val name = CustomerID.unapply(customer2ID).get
  • unapplyunapplySeq参见cs_java/scala/entity_components

应用程序对象

应用程序对象:实现有main(args: Array[String])方法的对象

  • main(args: Array[String])方法必须定义在单例对象中

  • 实际中可以通过extends App方式更简洁定义应用程序对象 ,trait App中同样是使用main函数作为程序入口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    trait App{
    def main(args: Array[String]) = {
    this._args = args
    for(proc <- initCode) proc()
    if (util.Propertie.propIsSet("scala.time")){
    val total = currentTime - executionStart
    Console.println("[total " + total + "ms")
    }
    }
    }
  • 包中可以包含多个应用程序对象,但可指定主类以确定包程序 入口

case object

样例对象:基本同case class

  • 对于不需要定义成员的域的case class子类,可以定义为 case object以提升程序速度
    • case object可以直接使用,case class需先创建对象
    • case object只生成一个字节码文件,case class生成 两个
    • case object中不会自动生成applyunapply方法

用途示例

创建功能性方法

1
2
3
4
5
6
7
8
9
10
11
package logging
object Logger{
def info(message: String): Unit = println(s"IFNO: $message")
}

// `import`语句要求被导入标识具有“稳定路径”
// 顶级单例对象全局唯一,具有稳定路径
import logging.Logger.info
class Test{
info("created projects")
}

Trait

特质:包含某些字段、方法的类型

  • 特点

    • 特质的成员方法、常/变量可以是具体、或抽象
    • 特质不能被实例化,因而也没有参数
    • 特质可以extends自类
  • 用途:在类之间共享程序接口、字段,尤其是作为泛型类型和 抽象方法

    • extend继承特质
    • with混入可以组合多个特质
    • override覆盖/实现特质默认实现
  • 子类型:需要特质的地方都可以使用特质的子类型替换

  • Java:trait被编译为interface,包含具体方法、常/变量 还会生成相应抽象类

Mixin

混入:特质被用于组合类

  • 类只能有一个父类,但是可以有多个混入

    • 混入和父类可能有相同父类
    • 通过Mixin trait组合类实现多继承
  • 多继承导致歧义时,使用最优深度优先确定相应方法

复合类型

复合类型:多个类型的交集,指明对象类型是某几个类型的子类型

1
<traitA> with <traitB> with ... {refinement}

sealed trait/sealed class

密封特质/密封类:子类确定的特质、类

1
2
3
4
sealed trait ST

case class CC1(id: String) extends ST
case object CC2 extends ST
  • 密封类、特质只能在当前文件被继承
  • 适合模式匹配中使用,编译器知晓所有模式,可提示缺少模式

自类型

自类型:声明特质必须混入其他特质,尽管特质没有直接扩展 其他特质

  • 特质可以直接使用已声明自类型的特质中成员

细分大类特质

1
2
3
4
5
6
7
8
9
10
11
12
13
trait User{
def username: String
}
trait Tweeter{
// 声明自类型,表明需要混入`User`特质
this: User =>
def tweet(tweetText: String) =
println(s"$username: $tweetText")
}
class VerifiedTweeter(val username_: String)
extends Tweeter with User{
def username = s"real $username_"
}

定义this别名

1
2
3
4
5
6
7
8
9
10
class OuterClass{
// 定义`this`别名,下面二者等价
outer =>
// outer: OuterClass =>
val v1 = "here"
class Innerclass {
// 方便在内部类中使用
println(outer.v1)
}
}

Universal Trait

Universal Trait:继承自Any、只有def成员、不作任何 初始化的trait

  • 继承自universal trait的value class同时继承该trait方法, 但是调用其会带来对象分配开销

泛型

  • 泛型类、泛型trait

    • 泛型类使用方括号[]接受类型参数
  • 泛型方法:按类型、值进行参数化,语法和泛型类类似

    • 类型参数在方括号[]中、值参数在圆括号()
    • 调用方法时,不总是需要显式提供类型参数,编译器 通常可以根据上下文、值参数类型推断
  • 泛型类型的父子类型关系不可传导

    • 可通过类型参数注释机制控制
    • 或使用类型通配符(存在类型)“构建统一父类”

存在类型/类型通配符

存在类型:<type>[T, U,...] forSome {type T; type U;...} ,可以视为所有<type>[]类型的父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 可以使用通配符`_`简化语法
// def printAll(x: Array[T] forSome {type T})
def printAll(x: Array[_]) = {
for (i <- x) {
print(i + " ")
}
println()
}

val a = Map(1 -> 2, 3 -> 3)
match a {
// 类型通配符语法可以用于模式匹配,但存在类型不可
case m: Map[_, _] => println(m)
}
  • 省略给方法添加泛型参数

类型边界约束

  • T <: A/T >: B:类型T应该是A的子类/B的父类

    • 描述is a关系
  • T <% STS的子类型、或能经过隐式转换为S子类型

    • 描述can be seen as关系
  • T : E:作用域中存在类型E[T]的隐式值

型变

型变:复杂类型的子类型关系与其组件类型的子类型关系的相关性

  • 型变允许在复杂类型中建立直观连接,利用重用类抽象
1
2
3
4
5
abstract class Animal{
def name: String
}
case class Cat(name: String) extends Animal
case class Cat(name: String) extends Animal

Covariant

协变:+A使得泛型类型参数A成为协变

  • 类型List[+A]A协变意味着:若AB的子类型,则 List[A]List[B]的子类型

  • 使得可以使用泛型创建有效、直观的子类型关系

1
2
3
4
5
6
7
8
9
10
11
12
def ConvarienceTest extends App{
def printAnimalNames(animals: List[Animal]): Unit = {
animals.foreach{ animal =>
println(animal.name)
}
}

val cats: List[Cat] = List(Cat("Winskers"), Cat("Tom"))
val dogs: List[Dog] = List(Dog("Fido"), Dog("Rex"))
printAnimalNames(cats)
printAnimalNames(dogs)
}

Contravariant

逆变:-A使得泛型类型参数A成为逆变

  • 同协变相反:若AB的子类型,则Writer[B]Writer[A]的子类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Printer[-A] {
def print(value: A): Unit
}
class AnimalPrinter extends Printer[Animal] {
def print(animal: Animal): Unit =
println("The animal's name is: " + animal.name)
}
class CatPrinter extends Printer[Cat]{
def print(cat: Cat): Unit =
println("The cat's name is: " + cat.name)
}

val myCat: Cat = Cat("Boots")
def printMyCat(printer: Printer[Cat]): Unit = {
printer.print(myCat)
}

val catPrinter: Printer[Cat] = new CatPrinter
val animalPrinter: Printer[Animal] = new AnimalPrinter

printMyCat(catPrinter)
printMyCat(animalPrinter)
  • 协变泛型

    • 子类是父类的特化、父类是子类的抽象,子类实例总可以 替代父类实例
    • 协变泛型作为成员
      • 适合位于表示实体的类型中,方便泛化、组织成员
      • 作为[部分]输出[成员]
    • 注意:可变的协变泛型变量不安全
  • 逆变泛型

    • 子类方法是父类方法特化、父类方法是子类方法抽象,子类 方法总可以替代父类方法
    • 逆变泛型提供行为、特征
      • 适合位于表示行为的类型中,方便统一行为
      • 仅作为输入
  • 协变泛型、逆变泛型互补

    • 对包含协变泛型的某类型的某方法,总可以将该方法扩展 为包含相应逆变泛型的类
1
2
trait Function[-T, +R]
// 具有一个参数的函数,`T`参数类型、`R`返回类型

Invariant

不变:默认情况下,Scala中泛型类是不变的

  • 可变的协变泛型变量不安全
1
2
3
4
5
6
7
8
9
10
11
12
13
class Container[A](value: A){
private var _value: A = value
def getValue: A = _value
def setValue(value: A): Unit = {
_value = value
}
}

// 若`A`协变
val catContainer: Container[cat] = new Container(Cat("Felix"))
val animalContainer: Container[Animal] = catCont
animalContainer.setValue(Dog("Spot"))
val cat: Cat = catContainer.getValue

Type Erasure

类型擦除:编译后删除所有泛型的类型信息

  • 导致运行时无法区分泛型的具体扩展,均视为相同类型
  • 类型擦除无法避免,但可以通过一些利用反射的类型标签解决

    • ClassTag
    • TypeTag
    • WeakTypeTag
    • 参见cs_java/scala/stdlib
  • Java:Java初始不支持泛型,JVM不接触泛型
    • 为保持向后兼容,泛型中类型参数被替换为Object、或 类型上限
    • JVM执行时,不知道泛型类参数化的实际类
  • Scala、Java编译器执行过程都执行类型擦除

抽象类型

抽象类型:由具体实现决定实际类型

  • 特质、抽象类均可包括抽象类型

    1
    2
    3
    4
    trait Buffer{
    type T
    val element: T
    }
  • 抽象类型可以添加类型边界

    1
    2
    3
    4
    5
    abstract class SeqBuffer extends Buffer{
    type U
    type T <: Seq[U]
    def length = element.length
    }
  • 含有抽象类型的特质、类经常和匿名类的初始化同时使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    abstract class IntSeqBuffer extends SeqBuffer{
    type U = Int
    }
    def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
    new IntSeqBuffer{
    type T = List[U]
    val element = List(elem1, elem2)
    }
    // 所有泛型参数给定后,得到可以实例化的匿名类
  • 抽象类型、类的泛型参数大部分可以相互转换,但以下情况无法 使用泛型参数替代抽象类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    abstract class Buffer[+T]{
    val element: T
    }
    abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T]{
    def length = element.length
    }
    def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
    new SeqBuffer[Int, List[Int]]{
    val element = List(e1, e2)
    }

包和导入

package

  • Scala使用包创建命名空间,允许创建模块化程序
  • 包命名方式

    • 惯例是将包命名为与包含Scala文件目录名相同,但Scala 未对文件布局作任何限制
    • 包名称应该全部为小写
  • 包声明方式:同一个包可以定义在多个文件中

    • 括号嵌套:使用大括号包括包内容
      • 允许包嵌套,可以定义多个包内容
      • 提供范围、封装的更好控制
    • 文件顶部标记:在Scala文件头部声明一个、多个包名称
      • 各包名按出现顺序逐层嵌套
      • 只能定义一个包的内容
  • 包作用域:相较于Java更加前后一致

    • 和其他作用域一样支持嵌套
    • 可以直接访问上层作用域中名称
      • 行为类似Python作用域,优先级高于全局空间中导入
      • 即使名称在不同文件中
  • 包冲突方案:Java中包名总是绝对的,Scala中包名是相对的, 而包代码可能分散在多个文件中,导致冲突

    • 绝对包名定位包__root__.<full_package_path>
      • 导入时:import __root__.<package>
      • 使用时:val v = new __root__.<class>
    • 串联式包语句隐藏包:包名为路径分段串,其中非结尾 包被隐藏
  • Scala文件顶部一定要package声明所属包,否则好像不会默认 导入scala.等包,出现非常奇怪的错误,如:没有该方法

import

  • import语句用于导入其他包中成员,相同包中成员不需要 import语句

    1
    2
    3
    4
    5
    6
    import users._
    import users.{User, UserPreferences}
    // 重命名
    import users.{UserPreferences => UPrefs}
    // 导入除`HashMap`以外其他类型
    import java.utils.{HashMap => _, _}
  • 若存在命名冲突、且需要从项目根目录导入,可以使用 __root__表示从根目录开始

  • scalajava.langobject Predef默认导入
  • 相较于Java,Scala允许在任何地方使用导入语句

包对象

包对象:作为在整个包中方便共享内容、使用的容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// in file gardening/fruits/Fruit.scala
package gardening.fruits

case class Fruit(name: String, color: String)
object Apple extends Fruit("Appple", "green")
object Plum extends Fruit("Plum", "blue")
object Banana extends Fruit("Banana", "yellow")

// in file gardening/fruits/package.scala
package gardening
package object fruits{
val planted = List(Apple, Plum, Banana)
def showFruit(fruit: Fruit): Unit = {
println(s"${fruit.name}s are ${fruit.color}")
}
}
  • 包对象中任何定义都认为时包自身的成员,其中可以定义 包作用域级任何内容

    • 类型别名
    • 隐式转换
  • 包对象和其他对象类似,每个包都允许有一个包对象

    • 可以继承Scala的类、特质
    • 注意:包对象中不能进行方法重载
  • 按照惯例,包对象代码通常放在文件package.scala

Annotations

注解:关联元信息、定义

  • 注解作用于其后的首个定义、声明
  • 定义、声明之前可以有多个注解,注解顺序不重要

确保编码正确性注解

  • 此类注解条件不满足时,会导致编译失败

@tailrec

@tailrec:确保方法尾递归

1
2
3
4
5
6
7
8
9
import scala.annotations.tailrec

def factorial(x: Int): Int = {
@tailrec
def factorialHelper(x: Int, accumulator: Int): Int ={
if (x == 1) accumulator
else factorialHelper(x - 1, accumulator * x)
}
}

影响代码生成注解

  • 此类注解会影响生成字节码

@inline

@inline:内联,在调用点插入被调用方法

  • 生成字节码更长,但可能运行更快
  • 不能确保方法内联,当且仅当满足某些生成代码大小的 启发式算法时,才会出发编译器执行此操作

@BeanProperty

@BeanProperty:为成员常/变量同时生成Java风格getter、setter 方法get<Var>()set<Var>()

  • 对成员变/常量,类编译后会自动生成Scala风格getter、 setter方法:<var>()<var>_$eq()

Java注解

  • Java注解有用户自定义元数据形式
    • 注解依赖于指定的name-value对来初始化其元素

@interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 定义跟踪某个类的来源的注解
@interface Source{
public String URL();
public String mail();
}

// Scala中注解应用类似构造函数调用
// 必须使用命名参数实例化Java注解
@Source(URL="http://coders.com",
mail="support@coders.com")
public class MyClass

// 若注解中只有单个[无默认值]元素、名称为`value`
// 则可以用类似构造函数的语法在Java、Scala中应用
@interface SourceURL{
public String value();
public String mail() defualt "";
}
@SourceURL("http://coders.com/")
public class MyClass
// 若需要给`mail`显式提供值,Java必须全部使用命名参数
@Source(URL="http://coders.com",
mail="support@coders.com")
public class MyClass
// Scala可以继续利用`value`特性
@SourceURL("http://coders.com/"
mail = "support@coders.com")
public class MyClass

标准库

Scala Package

Any

1
2
3
4
5
6
class Any{
// 判断对象是否是否为类型`T`实例
def isInstanceOf[T]
// 将对象强转为类型`T`实例
def asInstanceOf[T]
}
1
2
3
4
1.asInstanceOf[String]				// 报错,未定义隐式转换函数
1.isInstanceOf[String] // `false`
List(1),isInstanceOf[List[String]] // `true`,泛型类型擦除
List(1).asInstanceOf[List[String]] // 成功,泛型类型擦除

Option

1
2
3
4
5
6
7
8
class Option[T]{
// `Some`实例:返回被被包裹值
// `None`实例:返回参数(默认值)
def getOrElse(default?: T)
}

class Some[T](s?: T) extends Option[T]
object None extends Option[_] ???
  • 推荐使用Option类型表示可选值,明示该值可能为None

  • Option类型可以被迭代

    • Some(s):唯一迭代s
    • None:空
    1
    2
    val a = Some("hello")
    a.foreach(x => println(x.length))

Predef

1
2
3
4
object Predef extends LowPriorityImplicits{
// 返回运行过程中类型,具体实现由编译器填补
def classOf[T]: Class[T] = null
}

List

collection

mutable

Map

1
2
3
4
val a=Map((3,4), 5->6)
// 两种创建`Map`、二元组形式等价
a.map{case (a, b) => println(a, b)}
// `{case...}`为偏函数(或`Function1`)

immutable

reflect

runtime

universe

  • universe:提供一套完整的反射操作,可以反思性的检查 类型关系,如:成员资格、子类型
1
2
// 返回类型`T`“类型值”,可以用于比较
typeOf[T]
TypeTag
  • TypeTag:提供编译时具体类型的信息

    • 能获取准确的类型信息,包括更高的泛型类型
    • 但无法获取运行时值的类型信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import scala.reflect.runtime.universe.{TypeTag, TypeRef, typeTag}

    // 声明隐式参数列表
    def recognize[T](x: T)(implicit tag: TypeTag[T]): String =
    tag.tpe match {
    case TypeRef(utype, usymbol, args) =>
    List(utype, usymbol, args).mkString("\n")
    }

    val list: List[Int] = List(1,2)
    val ret = recognize(list)

    // 显式实例化`TypeTag`
    val tag = typeTag[List[String]]
  • WeakTypeTag:提供编译时包括抽象类型的信息

    • WeakTypeTag可以视为TypeTag的超集
    • 若有类型标签可用于抽象类型,WeakTypeTag将使用该标记
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import scala.reflect.runtime.universe.{WeakTypeTag, TypeRef, weakTypeRef}

    // 声明隐式参数列表
    def recognize[T](x: T)(implicit tag: WeakTypeTag[T]): String =
    tag.tpe match {
    case TypeRef(utype, usymbol, args) =>
    List(utype, usymbol, args).mkString("\n")
    }
    abstract class SAClass[T]{
    // 抽象类型
    val list: List[T]
    val result = Recognizer.recognize(list)
    println(result)
    }
    new SAClass[Int] { val list = List(2,3)}

    // 显式实例化`WeakTypeTag`
    val tag = weakTypeTag[List[String]]
  • 当需要TypeTag[T]WeakTypeTag[T]类型的隐式值tag时, 编译器会自动创建,也可以显式实例化
  • 以上类型探测通过反射实现,编译器根据传递实参推断泛型 参数T,由此确定特定类型标签隐式值

ClassTag

ClassTag:提供关于值的运行时信息

  • 不能在更高层次上区分,如:无法区分List[Int]List[String]
  • 是经典的老式类,为每个类型捆绑了单独实现,是标准的 类型模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import scala.reflect.{ClassTag, classTag}

// def extract[T: ClassTag](list: List[Any]) =
def extract[T](list: List[Any])(implicit ct: ClassTag[T]) =
list.flatMap{
case element: T => Some(element)
// 以上被翻译为如下,告诉编译器`T`的类型信息
// case element @ ct(_: T) =>
// 否则泛型`T`被删除,编译不会中断,但是无法正确工作
case _ => None
}

val list: List[Any] = List(1, "string1", List(), "string2")
val rets = extract[String](list)

// 显式实例化`ClassTag[String]`
val ct = classTag[String]
  • 当需要ClassTag[T]类型的隐式值ct时,编译器会自动创建

    • 也可以使用classTag显式实例化
  • ClassTag[T]类型值ct存在时,编译器将自动

    • 包装(_:T)类型模式为ct(_:T)
    • 将模式匹配中未经检查的类型测试模式转换为已检查类型

util

matching

Regex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import scala.util.matching.{Regex, Match}

class Regex(?pattern: String, ?group: String*){
def findAllIn(source: CharSequence): MatchIterator
def findAllMatchIn(source: CharSequence): Iterator[Match]
def findFirstIn(source: CharSequence): Option[String]
def findFirstMatchIn(source: CharSequence): Option[Match]
def replaceAllIn(target: CharSequence, replacer: (Match) => String): String
def replaceAllIn(target: CharSequence, replacement: String): String
def replaceFirstIn(target: CharSequence, replacement: String): String
// 此`unapplySeq`就应该是定义在类中
def unapplySeq(target: Any): Option[List[String]] = target match{
case s: CharSequence => {
val m = pattern matcher s
if (runMatcher(m)) Some((1 to m.groupCount).toList map m.group)
else None
}
// 等价于重载
case m: Match => unapplySeq(m.matched)
case _ => None
}
}

class Match{
def group(?key: Int): String
def group(?key: String): String
}
1
2
3
4
5
6
7
8
9
10
val keyValPattern: Regex = "([0-9a-zA-Z-#() ]+): ([0-9a-zA-Z-#() ]+)".r
// `String.r`可使任意字符串变成正则表达式
// 使用括号同时匹配多组正则表达式

val input: String =
"""backgroud-color: #A03300l
|background-image: url(img/header100.png);""".stripMargin

for(patternMatch(attr, value) <- keyValPattern.findAllMatchIn(input))
println(s"key: ${attr}, value: ${value}")

SBT

综述

SBT:simple build tools,Scala世界的Maven

  • 下载、解压、将SBT_HOME/bin添加进$PATH即可
  • SBT中包含的Scala可通过$ scala console交互式启动解释器

参数命令

  • clean:删除所有构建文件
  • compile:编译源文件:src/main/scalasrc/main/java
  • test:编译运行所有测试
  • console:进入包含所有编译文件、依赖的classpath的scala 解释器
    • :q[uit]:退出
  • run <args>:在和SBT所处同一虚拟机上执行项目main class
  • package:将src/main/resourcessrc/main/scalasrc/main/java中编译出class打包为jar
  • help <cmd>:帮助
  • reload:重新加载构建定义:build.sbtproject.scalaproject.sbt
  • inspect <key>:查看key描述
  • show <key[:subkey]>:查看key对应value执行结果
    • 例:show compile:dependencyClasspath
  • 以上命令可以在shell中作参数、在SBT命令行作命令
  • SBT命令行中历史记录查找同shell
  • ~前缀执行命令表示监视变化,源文件改变时自动执行该命令

项目结构

  • srcsrc中其他目录将被忽略

    • main:主源码目录
      • scala
      • java
      • resources:存储资源,会被打进jar包
        • 分布式执行则每个节点可以访问全体资源,相当于 broadcast
        • 访问时resources作为根目录,直接/列出相对 resource路径
    • test:测试源码目录
      • scala
      • java
      • resources
  • project:可以包含.scala文件,和.sbt文件共同构成 完整构建定义

    • Build.scala
    • plugins.sbt:添加sbt插件
  • target:包含构建出的文件:classes、jar、托管文件、 caches、文档

  • lib:存放非托管依赖

    • 其中jar被添加进CLASSPATH环境变量
  • build.sbt:包含构建定义

    • 其中隐式导入有
      • sbt.Keys._:包含内建key
      • sbt._

SBT配置

1
2
3
4
5
6
7
8
9
10
11
12
13
 # ~/.sbt/repositories
# 默认依赖仓库设置

[repositories]
local
<maven_repo_name>: <repo_address>
<ivy_repo_naem>: <repo_address>, <address_formation>

# 地址格式可能如下
[orgnanization]/[module]/(scala_[scalaVersion]/)(sbt_[sbtVersion]/)[revision]/[type]s/[artifact](-[classifier]).[ext]

# ali Maven2 repo
aliyun: https://maven.aliyun.com/nexus/content/groups/public/
  • 需要添加sbt启动参数-Dsbt.override.build.repos=true使 覆盖默认生效

Build Definition

构建定义:定义在build.sbt中、包含项目构建信息

  • 多工程.sbt构建定义:可为代码中定义多个子项目,结构灵活
  • bare .sbt构建定义
  • 构建定义可以包含位于project目录下的.scala下的文件, 用于定义常用函数、值

多工程.sbt构建定义

1
2
3
4
5
6
7
8
9
10
11
12
13
// 自定义`TaskKey`
lazy val hello = taskKey[Unit]("example task")
// 定义库ID
val derby = "org.apache.derby" % "derby" % "10.4.1.3"

// 创建名为`root`、位于当前目录的子项目
lazy val root = (project in file("."))
// 在`.settings`方法中设置*setting expression*键值对
.settings(
name := "hello",
hello := {prinln("hello")},
libraryDependencies += derby
)
  • 构建定义拥有不可变的Setting[T]类型映射描述项目属性, 是会影响sbt保存键值对的map的转换

    • T:映射表中值类型
    • Setting描述.settings是对映射表的转换,增加新键值、 追加至已有值,转换后的映射成为sbt新映射
  • build.sbt文件中除设置外,可以包含valdef定义

    • 所有定义都在设置之前被计算,无论在文件中所处位置
    • 一般使用lazy val避免初始化顺序问题

Bare.sbt构建定义

1
2
3
4
name := "hello"
version := "1.0"
scalaVersion := "2.12.8"
library.Dependencies += "org.apache.derby" % "derby" % "10.4.1.3"
  • bare .sbt构建定义由Setting[_]表达式列表组成

Keys

  • SettingKey[T]:值仅在子项目载入时计算一次
  • TaskKey[T]:值每次都需要被计算,可能有副作用
  • InputKey[T]:值以命令行参数作为输入的任务
  • 可以通过各自创建方法创建自定义key

    1
    2
    // 给定value(返回)类型、键值对描述
    lazy val hello = taskKey[Unit]("task key demo")
  • 在sbt交互模式下,可以输入key名称获取、执行value

    • setting key:获取、显示value
    • task key:执行value,但不会显示执行结果,需要 show <task>才会打印执行结果
  • Key可以视为为项目定义的属性、trigger
  • taskiness(每次执行)可以视为task key的属性

sbt.Keys内建Key

  • 内建Key中泛型参数已经确定,定制需要满足类型要求

项目属性

  • name:项目名称,默认为项目变量名
  • baseDirectory:项目根目录
  • sourceDirectories:源码目录
    • compile:_:编译时设置
  • sourceDirectory:源码上层目录?

依赖相关

  • unmanageBase:指定非托管依赖目录
  • unmanagedJars:列举unmanagedBase目录下所有jar 的task key
  • dependencyClasspath:非托管依赖classpath
    • compile:_:编译时设置
    • runtime:_:运行时设置
  • libraryDependecies:指定依赖、设置classpath
    • 直接列出
    • Maven POM文件
    • Ivy配置文件
  • resolvers:指定额外解析器,Ivy搜索服务器指示
  • externalResolvers:组合resolvers、默认仓库的task key
    • 定制其以覆盖默认仓库

运行相关

  • package:打包系列Task

    • 类型:TaskKey[File]的task key
    • 返回值:生成的jar文件
  • compile:编译系列Task

.sbt特殊方法

  • 常用类型String等的方法仅在.sbt中可用
  • 方法的具体行为、返回值略有差异

XXXXKey[T]

  • :=:给Setting[T]赋值、计算,并返回Setting[T]

    • SettingKey[T].:=返回Setting[T]
    • TaskKey[T].:=返回Setting[Task[T]]
  • in:获取其他Key的子Key

    1
    sourceDirectories in Compile += Seq(file("1"), file("2"))

SettingKey[T]

  • +=追加单个元素至列表
  • ++=:连接两个列表

String

  • %:从字符串构造Ivy ModuleID对象
  • %%:sbt会在actifact中加上项目的scala版本号
    • 也可手动添加版本号替代
    • 很多依赖会被编译给多个Scala版本,可以确保兼容性
  • at:创建Resolver对象

依赖

非托管依赖

非托管依赖:lib/目录下的jar文件

  • 其中jar被添加进classpath
    • compiletestrunconsole都成立
    • 可通过dependencyClasspath改变设置[某个]classpath

相关Key使用

1
2
3
4
5
6
7
// 定制非托管依赖目录
dependencyClasspath in Compile += <path>
dependencyClasspath in Runtime += <path>
// 定制非托管目录
unmanagedBase := baseDirectory.value / "custom_lib"
// 清空`compile`设置列表
unmanagedJars in Compile := Seq.empty[sbt.Attributed[java.io.File]]

托管依赖

托管依赖:由sbt根据build.sbt中设置自动维护依赖

  • 使用Apache Ivy实现托管依赖
  • 默认使用Maven2仓库

格式

1
2
3
dep_exp ::= <groupID> % <artifactID> % <revision>[% <configuraion>] [% "test" | % Test] [% "provided"]

resolver_exp ::= <name> at <location>
  • groupID
  • acrtifactID:工件名称
  • revision:版本号,无需是固定版本号
    • "latest.integration"
    • "2.9.+"
    • [1.0,)
  • 可选选项
    • "test"|Test:仅在Test配置的classpath中出现
    • "provided":由环境提供,assembly打包时将不打包该 依赖
  • name:指定Maven仓库名称
  • location:服务器地址

依赖添加

1
2
3
4
5
6
7
8
9
10
11
// `sbt.Keys`中依赖声明
val libraryDependencies = settingKey[Seq[ModuleID]]("Delares managed dependencies")

// 添加单个依赖
libraryDependencies += dep_exp
// 添加多个依赖
libraryDependencies ++= Seq(
dep_exp,
dep_exp,
<groupID> %% <artifactID> % <revision>
)

解析器添加

1
2
3
4
5
6
7
8
9
10
11
12
// `sbt.Keys`中解析器声明
val resolvers += settingKeys[Seq[Resolver]]("extra resolvers")

// 添加本地Maven仓库
resolvers += resolver_exp
resolvers += Resolver.mavenLocal
resolvers += "Loal Maven Repo" at "file://" + Path.userHome.absolutePath+"/.m2/repository"

// 将自定义解析器至于默认`externalResolvers`前
externalResolvers := Seq(
resolver_exp
) ++ externalResolvers.values
  • externalResolvers中包含默认解析器,sbt会将此列表值拼接 至resolvers之前,即仅修改resolvers仍然会有限使用默认 解析器

其他配置

project/plugins.sbt

1
2
3
// assembly插件
// `assembly`:将依赖加入jar包,修改jar包配置文件
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6")