Scala 基础-杂记(第零章)

1. 关键字

trait

相当于 Java 中的接口,可以定义抽象方法,还可以定义字段和方法的实现,且一个类可以混合任意数量的 Trait。

1
2
3
4
5
6
7
8
// 定义一个 trait
trait Flyable {
var maxHeight:Int // 声明一个抽象字段
def fly() // 定义一个抽象方法
def breathe() { // 定义一个具体的方法
println("呼吸...")
}
}

implicit

隐式转换函数是指在同一个作用域下面,一个给定输入类型并自动转换为指定返回类型的函数,这个函数和函数名、入参名均无关,只和入参类型以及返回类型有关

1.实战

display 只接受 String 类型的入参,否则编译会出错

1
def display(input:String):Unit = println(input)

当想其能支持 Int 类型的入参时,除了我们重新定一个 def display(input:Int):Unit = println(input) 这样的函数以外,还可以在相同的作用域内用 implicit 关键字定义一个隐式转换函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
object ImplicitDemo {

def display(input:String):Unit = println(input)

// 定义2个隐式转换函数,实现 Int、Boolean 类型的入参
implicit def typeConvertor(input:Int):String = input.toString
implicit def typeConvertor(input:Boolean):String = if(input) "true" else "false"

def main(args: Array[String]): Unit = {
display("1212")
display(12)
display(true)
}
}

2. 符号

1. ->, <-

  • -> : 创建 map 时使用,表示映射
  • <- : 左箭头在遍历时使用
1
2
3
4
5
6
7
8
9
// map 创建
val score = Map("小明" -> 50, "小红" -> 60)

// 遍历
val people = List("小明","小红","小兰")
for(man <- people){
println(man)
}

2. =>

可以看做是创建函数实例的语法糖。例如:A => TA,B => T 表示一个函数的输入参数类型是 AA,B,返回值类型是 T
() => T 表示函数输入参数为空,而 A => Unit 则表示函数没有返回值。

3. _

1. 匿名函数中参数

当匿名函数参数未被使用或只使用一次时,可用它来代替

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 未使用
scala> list.foreach(_ => println("Hello Scala"))
Hello Scala

// 只使用一次
scala> val list = List(5,3,7,9,1)
list: List[Int] = List(5, 3, 7, 9, 1)

// 等同于 list.map(x => x * 10)
scala> list.map(_ * 10)
res0: List[Int] = List(50, 30, 70, 90, 10)

// 等同于 list.sortWith((x, y) => x < y)
scala> list.sortWith(_ < _)
res1: List[Int] = List(1, 3, 5, 7, 9)

2. 通配符

可用在泛型、模式匹配和导入语句中。

在泛型中类似 java 中的 ?。

1
2
3
4
5
// 泛型中使用,等同于 java 中 list<?>
scala> def testPrint(l: List[_]) = {
| list.foreach(x => println(x))
| }
testPrint: (l: List[_])Unit

在模式匹配中,可以用来代替 Java switch-case 表达方式中的 default 之外,还可以占位表示其他元素甚至类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
str match {
case 1 => "one"
case 2 => "two"
// 如同 java 中的 default
case _ => "anything other than one and two"
}

expr match {
// 以1开头,且长度为 3 的 List
case List(1,_,_) => "a list with three element and the first element is 1"
// 长度大于等于 0 的 List
case List(_*) => "a list with zero or more elements"
// 键和值类型都为任意类型的Map
case Map[_,_] => "matches a map with any key type and any value type"
case _ =>
}

可以实现 Java import 语句中星号的作用,还能导入时做重命名,以及忽略某些类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// import java.util.concurrent.*
scala> import java.util.concurrent._
import java.util.concurrent._

// import java.util.*,并 将ArrayList 重命名为 al
scala> import java.util.{ArrayList => al, _}
import java.util.{ArrayList => al, _}

// import java.util.*,但不导入 Timer 类
scala> import java.util.{Timer => _, _}
import java.util.{Timer => _, _}

// import static java.lang.Math.*
scala> import java.lang.Math._
import java.lang.Math._

3. 变量默认值初始化

用下划线可以自动在变量声明时,将其赋予默认的初始值。

1
2
3
4
5
6
7
8
scala> var name : String = _
name: String = null

scala> var count : Int = _
count: Int = 0

scala> var avg : Double = _
avg: Double = 0.0

4. 访问元组(tuple)

下划线后面跟上数字 k,可以当作索引表示元组中的第 k 个元素。当要忽略元组中的某个值时,也可以用下划线代替它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scala> val tuple = ("Tom", 173.5, Seq(22,66,88))
tuple: (String, Double, Seq[Int]) = (Tom,173.5,List(22, 66, 88))

scala> tuple._1
res1: String = Tom

scala> tuple._2
res2: Double = 173.5

scala> tuple._3
res3: Seq[Int] = List(22, 66, 88)

scala> val (first, _, third) = tuple
first: String = LMagics
third: Seq[Int] = List(22, 66, 88)

5. 声明 setter 方法

Scala 中没有显式的 setter 方法,将 getter 方法的命名后加上一个下划线,当做 setter 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scala> class Test {
| private var pCount = 0
| def count = pCount // getter
| def count_= (c : Int) = { // setter
| require(c > 0)
| pCount = c
| }
| }
defined class Test

scala> val test = new Test()
test: Test = Test@27d5a580

scala> test.count = 7
test.count: Int = 7

6. 变长参数的转化

下划线与星号连用,可以将序列转化为变长参数的序列,方便调用变长参数的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
scala> def sum(args: Int*) = {
| var result = 0
| for (arg <- args) result += arg
| result
| }
sum: (args: Int*)Int

scala> val sum1 = sum(7,8,9,10,11,12,13,14)
sum1: Int = 84

// 如果只写 7 to 14,会报错
scala> val sum2 = sum(7 to 14: _*)
sum2: Int = 84

7. 将方法转换成函数

函数在 Scala 中是一种对象实例,因此它可以赋值给变量,也可以作为参数。如果方法在赋值时直接写名称的话,编译器会认为是对方法的调用,因此会报没有参数列表的错误。在方法名称后加一个下划线,会将其转化为偏应用函数(partially applied function),就能直接赋值了。

1
2
3
4
5
6
7
8
9
10
11
12
scala> def twoSum(a: Int, b: Int) = a + b
twoSum: (a: Int, b: Int)Int

scala> val twoSumFunc = twoSum _
twoSumFunc: (Int, Int) => Int = <function2>

scala> list foreach println _
5
3
7
9
1

8. 定义偏应用函数

偏应用函数是不提供或者只提供部分参数调用时返回的函数,我们也可以用下划线来代替那些不提供的参数。

1
2
3
4
5
scala> def threeMult(a: Int, b: Int, c:Int) = a * b * c
threeMult: (a: Int, b: Int, c: Int)Int

scala> val parThreeMult = (8, _: Int, 42)
parThreeMult: Int => (Int, Int, Int) = <function1>

4. 上界(<:)、下界(>:)、协变(+)、逆变(-)

1. 上下界

常用在泛型中,类似 java 的 <S extends T>, <U spuer T>

  • S <: T : 类型上界的定义,S 必须是类型 T 的子类或本身
  • U >: T : 类型下界的定义,U 必须是类型 T 的父类或本身

2. 协变逆变

常用在泛型中,如 List[T],如果对于 A 是 B 的子类型,满足 List[A] 也符合 List[B] 的子类型,那么就称为 covariance(协变),相反,则称为 contravariance(逆变) 。
即,协变是指可以将子类集合可以转换成父类集合,符合里氏替换原则(子类型必须能够替换掉他们的基类型),逆变则是反过来。

1
2
3
4
5
6
7
8
9
10
11
12
class Animal {}
class Bird extends Animal {}

//协变,cov 可以被赋值给 cov2
class Covariant[+T](t:T) {}
val cov = new Covariant[Bird](new Bird)
val cov2:Covariant[Animal] = cov

// 逆变,c 可以被赋值给 c2
class Contravariant[-T](t:T) {}
val c: Contravariant[Animal] = new Contravariant[Animal](new Animal)
val c2: Contravariant[Bird] = c

3. 特殊类

1. Range

是一种数据结构,一种容器,是一种特殊的带索引的不可变的等差数字序列。

1. 声明

除了可以创造整数型序列,也可以是字符型、单精度浮点型

1
2
3
4
5
6
7
8
9
10
11
// 1. 常用声明是类形式,3个参数是起始、终止、步长
val r = new Range(1,5,2)
r.foreach(print(_)) // 13

// 2. 使用 to, until, by
val a1 = 1 to 5 by 1 // 12345
val a2 = 1.to(5).by(2) // 135
val a3 = 1 until 5 // 1234

// 浮点型
val b = .5f to 10.8f by 0.5f

4. 总览

scala-template