Scala入门

1、变量声明

1.1、 声明val变量

例如,val result = 1 + 1
后续这些常量是可以继续使用的,例如,2 * result
但是注意常量声明后,是无法改变它的值的,例如,result = 1,会返回error: reassignment to val的错误信

1.2、 声明var变量

如果要声明值可以改变的引用,可以使用var变量。例如,val result = 1,result = 2。但是在scala程序中,通常建议使用val,也就是常量,因为在网络传输中,如果是变量,可能会担心值被修改

1.3、 指定类型

无论声明val变量,还是声明var变量,都可以手动指定其类型,如果不指定的话,scala会自动根据值,进行类型的推断
例如,val name: String = null
例如,val name: Any = "Tom"

声明多个变量:可以将多个变量放在一起进行声明。
例如,val name1, name2:String = null
例如,val num1, num2 = 100

2、基本类型

基本数据类型:Byte、Char、Short、Int、Long、Float、Double、Boolean
注意 : scala没有基本数据类型与包装类型的概念,统一都是类。scala自己会负责基本数据类型和引用类型的转换操作
包装类型都是可以这样调用 : 例如,100.toString(),1.to(10)

2.1、类型的加强版

例如,String类通过StringOps类增强了大量的函数,"Hello".intersect(" World")
例如,Scala还提供了RichInt、RichDouble、RichChar等类型,RichInt就提供了to函数,1.to(10),此处Int先隐式转换为RichInt,然后再调用其to函数

2.2、基本操作符

scala的算术操作符与java的算术操作符也没有什么区别,比如+、-、*、/、%等,以及&、|、^、>>、<<等
注意,在scala中,这些操作符其实是数据类型的函数,比如1 + 1,可以写做1.+(1),例如,1.to(10),又可以写做1 to 10
scala中没有提供++、--操作符,我们只能使用+和-,比如counter++是错误的,必须写做counter += 1

3、函数调用与apply()函数

3.1、函数调用方式

在scala中,函数调用如下 :
例如,import scala.math._,sqrt(2),pow(2, 4),min(3, Pi)
如果调用函数时,不需要传递参数,则scala允许调用函数时省略括号的,例如,"Hello World".distinct

3.2、apply函数

Scala中的apply函数是非常特殊的一种函数,在Scala的object中(半生对象),可以声明apply函数。而使用“类名()”的形式,其实就是“类名.apply()”的一种缩写。通常使用这种方式来构造类的对象,而不是使用“new 类名()”的方式
例如,Array(1, 2, 3, 4),实际上是用Array object的apply()函数来创建Array类的实例,也就是一个数组

4、条件控制与循环

4.1、if表达式

if表达式的定义 :
在Scala中,if表达式是有值的,就是if或者else中最后一行语句返回的值
if表达式赋予一个变量,例如,val isAdult = if (age > 18) 1 else 0

if表达式的类型推断:
由于if表达式是有值的,而if和else子句的值类型可能不同,Scala会自动进行推断,取两个类型的公共父类型
例如,if(age > 18) 1 else 0,表达式的类型是Int,因为1和0都是Int
例如,if(age > 18) "adult" else 0,此时if和else的值分别是String和Int,则表达式的值是Any,Any是String和Int的公共父类型

注意 : 如果if后面没有跟else,则默认else的值是Unit,也用()表示,类似于java中的void或者null。例如,val age = 12; if(age > 18) "adult"。此时就相当于if(age > 18) "adult" else ()

将if语句放在多行中
默认情况下,REPL只能解释一行语句,但是if表达式通常需要放在多行。可以使用{}的方式

result = if (age > 18) {
  println("成年人")
  "aduit"
} else if (age > 12) {
  println("未成年")
  "teenager"
} else {
  println("儿童")
  "child"
}

4.2、语句终结符、块表达式

默认情况下,Scala不需要语句终结符,默认将每一行作为一个语句

一行多条语句,就必须使用语句终结符;,比如 var a, b, c = 0; if(a < 10) { b = b + 1; c = c + 1 },对于多行语句,还是会适应花括号的方式

if(a < 10) {
  b = b + 1
  c = c + 1
}

块表达式,块表达式,指的就是{}中的值,其中可以包含多条语句,最后一个语句的值就是块表达式的返回值

val a = 9
var b = 0
val result = if (a < 10) {
  b += 1
  a + 1
} else {
  b -= 1
  a - 1
}

最终 result = 10

4.3、输入和输出

print和println:print打印时不会加换行符,而println打印时会加一个换行符
printf: printf可以用于进行格式化(和C中一样)
例如 : printf("Hi, my name is %s, I'm %d years old.", "Tom", 30)
readLine: readLine允许我们从控制台读取用户输入的数据,类似于java中的System.in和Scanner的作用

println("Your names is : ")
val name: String = scala.io.StdIn.readLine()

println("Your age is : ")
val age: Int = scala.io.StdIn.readInt()

println(
  s"""
    | name : ${name}
    | age : ${age}
    |""".stripMargin)


输出结果 : 
Your names is : 
zhangsan
Your age is : 
30

 name : zhangsan
 age : 30

4.4、循环

while do循环 : Scala有while do循环,基本与Java相同

var n = 10
while (n > 0) {
  println(n)
  n -= 1
}

Scala没有for循环,只能使用while替代for循环,或者使用简易版的for语句

val count = 10
for (num  <- 1 to count)
  println(num)

// 1 until count,说明不能到count
for (num <- 1 until  count)
  println(num)

Scala没有提供类似于Java中的break语句,但是可以使用boolean、return或者Breaks的break函数来替代

for (num <- 1 to count) {
  if (num == 5) {
    return
  }
  println("num : " + num)
}

4.5、多重循环

冒泡排序 :

val arrs: Array[Int] = Array(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

for (i <- 0 until(10); j <- 0 until(9 - i)) {
  if (arrs(j) > arrs(j + 1)) {
    var temp = arrs(j)
    arrs(j) = arrs(j + 1)
    arrs(j + 1) = temp
  }
}
println(arrs.mkString(","))

if守卫 : 取偶数

for (k <- 1 to 10 if k % 2 == 0) println(k)

for推导式:构建集合

val ints: immutable.IndexedSeq[Int] = for (i <- 1 to 10) yield i

5、函数入门

5.1、函数的定义与调用

在Scala中定义函数时,需要定义函数的函数名、参数、函数体

  def main(args: Array[String]): Unit = {

    sayHello("Tom", 30)

  }

  def sayHello(name : String, age : Int) = {
    if (age > 18) {
      printf("hi %s,you are adult\n", name)
    } else {
      printf("hi %s,you are not adult\n", name)
    }
  }

Scala要求必须给出所有参数的类型,但是不一定给出函数返回值的类型,只要右侧的函数体中不包含递归的语句,Scala就可以自己根据右侧的表达式推断出返回类型

单行函数 :

def sayHello2(name : String) = println("hello " + name)

如果函数体有多行代码,可以使用代码块的方式包裹多行代码,代码块中最后一行的返回值就是整个函数的返回值
累加功能:

def sum(n : Int) = {
    var sum = 0
    for (i <- 1 to n) sum += i
    sum
  }

5.2、递归函数与返回类型

如果在函数体内递归调用函数自身,则必须手动给出函数的返回类型

例如,实现经典的斐波那契数列:
9 + 8; 8 + 7 + 7 + 6; 7 + 6 + 6 + 5 + 6 + 5 + 5 + 4; ....

def fab(n : Int) : Int = {
  if (n <= 1) 1
  else fab(n - 1) + fab(n - 2)
}

5.3、默认参数

在Scala中,有时我们调用某些函数时,不希望给出参数的具体值,而希望使用参数自身默认的值,此时就定义在定义函数时使用默认参数

def say(name : String, age : Int, hobby : String = "coding")= {
    s"my name is ${name}, age is : ${age}, hobby is : ${hobby}"
}

如果给出的参数不够,则会从作往右依次应用参数

5.4、带名参数

在调用函数时,也可以不按照函数定义的参数顺序来传递参数,而是使用带名参数的方式来传递
比如 : println(say(age = 30, name = "zhangsan", hobby = "hehe"))

还可以混合使用未命名参数和带名参数,但是未命名参数必须排在带名参数前面
println(say("zhagnsan", age = 20))

5.5、变长参数

在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数定义函数

def sum(nums: Int*) = {
  var result = 0
  for (num <- nums) result += num
  result
}

使用序列调用变长参数
在如果想要将一个已有的序列直接调用变长参数函数,是不对的。比如val s = sum(1 to 5)。此时需要使用Scala特殊的语法将参数定义为序列,让Scala解释器能够识别

val s = sum(1 to 5: _*)

案例:使用递归函数实现累加

def sum2(nums: Int*): Int = {
  if (nums.length == 0) 0
  else nums.head + sum2(nums.tail: _*)
}

5.6、过程

在Scala中,定义函数时,如果函数体直接包裹在了花括号里面,而没有使用=连接,则函数的返回值类型就是Unit。这样的函数就被称之为过程。过程通常用于不需要返回值的函数

def sayHello4(name : String){
    println("Hello," + name)
}

def sayHello5(name: String): Unit = {
    println("Hello," + name)
}

6、lazy值

在Scala中,提供了lazy值的特性,也就是说,如果将一个变量声明为lazy,则只有在第一次使用该变量时,变量对应的表达式才会发生计算。这种特性对于特别耗时的计算操作特别有用,比如打开文件进行IO,进行网络IO等

import scala.io.Source._
// datas/user1.json文件是不存在的,也不会保持,只有到真正使用运行的时候才会报错
lazy val lines1: Iterator[String] = fromFile("datas/user1.json").getLines()
val lines2: Iterator[String] = fromFile("datas/user.json").getLines()

7、异常

使用match case的方式进行异常匹配

try {
  throw new IOException("user defined exception.")
} catch {
  case e : IllegalArgumentException => println("illegal argument")
  case e : IOException => println("illegal argument")
  case _ => println("other exception.")
}

8、Array & ArrayBuffer

8.1、Array

在Scala中,Array代表的含义与Java中类似,也是长度不可改变的数组。此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上是Java数组。例如字符串数组在底层就是Java的String[],整数数组在底层就是Java的Int[]

// 数组初始化后,长度就固定下来了,而且元素全部根据其类型初始化
val a = new Array[Int](10)
a(0)
a(0) = 1
val a = new Array[String](10)

// 可以直接使用Array()创建数组,元素类型自动推断
val a = Array("hello", "world")
a(0) = "hi"
val a = Array("tom", 30)

8.2、ArrayBuffer

在Scala中,如果需要类似于Java中的ArrayList这种长度可变的集合类,则可以使用ArrayBuffer

val arrayBuffer = ArrayBuffer[String]()
// += 就是加入一个元素
arrayBuffer += "zhagnsan"
arrayBuffer += "lisi"
arrayBuffer += "wangwu"

// -= 就是去掉一个元素
arrayBuffer -= "zhagnsan"

val arrayBuffer2 = ArrayBuffer[String]("aa", "bb")
// 这个其实相当于Java中的addAll
arrayBuffer ++= arrayBuffer2

for (item <- arrayBuffer) {
  println(item)
}

结果输出 : 
lisi
wangwu
aa
bb

// 使用trimEnd()函数,可以从尾部截断指定个数的元素
arrayBuffer.trimEnd(2)
println(arrayBuffer)

结果输出 :
ArrayBuffer(lisi, wangwu)

其他功能 :

// 使用insert()函数可以在指定位置插入元素
// 但是这种操作效率很低,因为需要移动指定位置后的所有元素
b.insert(5, 6)
b.insert(6, 7, 8, 9, 10)
// 使用remove()函数可以移除指定位置的元素
b.remove(1)
b.remove(1, 3)
// Array与ArrayBuffer可以互相进行转换
b.toArray
a.toBuffer

8.3、遍历

// 使用for循环和until遍历Array / ArrayBuffer
// 使until是RichInt提供的函数
for (i <- 0 until b.length)
  println(b(i))
// 跳跃遍历Array / ArrayBuffer
for(i <- 0 until (b.length, 2))
  println(b(i))
// 从尾部遍历Array / ArrayBuffer
for(i <- (0 until b.length).reverse)
  println(b(i))
// 使用“增强for循环”遍历Array / ArrayBuffer
for (e <- b)
  println(e)


val a = Array(1, 2, 3, 4, 5)
val sum = a.sum
// 获取数组最大值
val max = a.max
// 对数组进行排序
scala.util.Sorting.quickSort(a)
// 获取数组中所有元素内容
a.mkString
a.mkString(", ")
a.mkString("<", ",", ">")
// toString函数
a.toString
b.toString

9、数组操作之数组转换

9.1、使用yield方式

// 对Array进行转换,获取的还是Array
val arr1 = Array(1,2,3,4,5)
val arr2 = for (ele <- arr1) yield ele * ele

// 对ArrayBuffer进行转换,获取的还是ArrayBuffer
val arr3 = ArrayBuffer[Int]()
arr3 += (1,2,3,4,5)
val arr4 = for (ele <- arr3) yield ele * ele

// 结合if守卫,仅转换需要的元素
val arr5 = for (ele <- arr3 if ele % 2 == 0) yield ele * ele

9.2、函数式编程转换数组

// 使用函数式编程转换数组
val arr6: Array[Int] = arr1.filter(_ % 2 == 0).map(_ * 2)

10、Map与Tuple

10.1、Map

创建Map:

// 创建一个不可变的Map
val users1: Map[String, Int] = Map("zhangsan" -> 23, "lisi" -> 30, "wangwu" -> 40)
//    users1("zhagnsan") = 20 因为Map默认是不可变的,所以这个是不能改变的

val users2: mutable.Map[String, Int] = scala.collection.mutable.Map("zhagnsan" -> 23, "lisi" -> 30, "wangwu" -> 40)
users2("zhagnsan") = 20
users2 += (("zhaoliu", 20))

// 使用另外一种方式来定义Map元素
val users3: Map[String, Int] = Map(("zhangsan", 20), ("lisi", 30), ("wangwu", 40))

// 创建一个空的HashMap
val users4 = new mutable.HashMap[String, Int]()

访问Map的元素:

// 获取指定key对应的value,如果key不存在,会报错
val tomAge = ages("Tom")

// 使用contains函数检查key是否存在
val tomAge = if (ages.contains("Tom")) ages("Tom") else 0

// getOrElse函数
val tomAge = ages.getOrElse("Tom", 0)

修改Map的元素:

// 更新Map的元素
users2("Tom") = 31
// 增加多个元素
users2 += ("Mike" -> 35, "Tom" -> 40)
// 移除元素
users2 -= "Mike"

// 更新不可变的map
val users5 = users1 + ("Mike" -> 36, "Tom" -> 40)
// 移除不可变map的元素
val users6 = users1 - "Tom"

遍历Map :

// 遍历map的entrySet
for ((key, value) <- users1) println(key + " " + value)
// 遍历map的key
for (key <- users1.keySet) println(key)
// 遍历map的value
for (value <- users1.values) println(value)
// 生成新map,反转key和value
for ((key, value) <- users1) yield (value, key)

10.2、SortedMap和LinkedHashMap

// SortedMap可以自动对Map的key进行排序
val users5 = mutable.SortedMap("zhangsan" -> 30, "lisi" -> 34, "wangwu" -> 45)

val user6 = new mutable.LinkedHashMap[String, Int]
user6("zhangsan") = 30
user6("lisi") = 34
user6("wangwu") = 45

10.3、Tuple

其实就是简单的键值对,简单理解就是Map中的一个元素,可以通过_1、_2、_n...下标来访问

val t = ("zhangsan", 20)
println(t._1)
println(t._2)

// zip操作
val names = Array("zhangsan", "lisi", "wagnwu")
val ages = Array(30, 20, 25)
val users = names.zip(ages)
for ((name, age) <- users) println(name + "->" + age)

11、面向对象

11.1、基础

object User {
  var user : User = new User

  def apply(): User = {
    user
  }

  def adjustSalary(sal : Int): Unit = {
    user.salary = sal
  }

  def getName = user.name
  def setName(name : String): Unit = {
    user.name = name
  }

  def info = user.info()
}

class User {
  // 国家,默认中国,不能改哈
  private[this] val country = "中国"

  // 薪水,薪水是不能随便改的,比如说只能公司给你改
  private var salary = 1000

  // 姓名是可以修改的,自己修改即可
  var name : String = _

  def info(): Unit = {
    println("username :  " + name + ", your country is : " + country + ", salary is : " + salary)
  }
}

调用处 :

// 普通用户操作
val commonUser = new User
commonUser.name = "zhangsan"
commonUser.info

// 管理用户操作
val adminUser = User
adminUser.setName("zhangsan")
adminUser.adjustSalary(10000)
adminUser.info

打印结果 :
username :  zhangsan, your country is : 中国, salary is : 1000
username :  zhangsan, your country is : 中国, salary is : 10000

注意 :

  1. 定义不带private的var field,此时scala生成的面向JVM的类时,会定义为private的name字段,并提供public的getter和setter方法
  2. 而如果使用private修饰field,则生成的getter和setter也是private的,只有伴生对象可以访问
  3. 如果定义val field,则只会生成getter方法
  4. 如果不希望生成setter和getter方法,则将field声明为private[this],谁都修改不了

11.2、自定义getter与setter

Scala的getter和setter方法的命名与java是不同的,是field和field_=的方式

注意 :

如果只是希望拥有简单的getter和setter方法,那么就按照scala提供的语法规则,根据需求为field选择合适的修饰符就好:var、val、private、private[this]
但是如果希望能够自己对getter与setter进行控制,则可以自定义getter与setter方法
自定义setter方法的时候一定要注意scala的语法限制,签名、=、参数间不能有空格

class Stu {

  private var username: String = "wuming"

  def name = "my name is :" + username
  def name_=(newName:String) {
    username = newName
  }
}

调用处 : 
val stu = new Stu
println(stu.name)
stu.name = "zhangsan"
println(stu.name)

11.3、仅暴露field的getter方法

如果你不希望field有setter方法,则可以定义为val,但是此时就再也不能更改field的值了
但是如果希望能够仅仅暴露出一个getter方法,并且还能通过某些方法更改field的值,那么需要综合使用private以及自定义getter方法

此时,由于field是private的,所以setter和getter都是private,对外界没有暴露;自己可以实现修改field值的方法;自己可以覆盖getter方法

class Stu {

  private var username: String = "wuming"

  def name = "my name is :" + username
  def updateName(newName:String) {
    if (newName == "zhangsan") username = newName
    else println("newName : " + newName + " deny")
  }
}

11.4、private[this]的使用

如果将field使用private来修饰,那么代表这个field是类私有的,在类的方法中,可以直接访问类的其他对象的private field

这种情况下,如果不希望field被其他对象访问到,那么可以使用private[this],意味着对象私有的field,只有本对象内可以访问到

11.4、Java风格的getter和setter方法

class Stu {
  @BeanProperty var username: String = "wuming"
}

11.5、辅助constructor

Scala中,可以给类定义多个辅助constructor,类似于java中的构造函数重载辅助constructor之间可以互相调用,而且必须第一行调用主constructor

class Stu {

  private var name = ""
  private var age = 0

  def this(name : String) {
    this()
    this.name = name
  }

  def this(name : String, age : Int) {
    this()
    this.name = name
    this.age = age
  }
}

11.6、主constructor

Scala中,主constructor是与类名放在一起的,与java不同。而且类中,没有定义在任何方法或者是代码块之中的代码,就是主constructor的代码

class Stu(val name : String, val age : Int) {
  println(s"my name is : ${name}, age is : ${age}")
}

**主constructor中还可以通过使用默认参数,来指定参数的默认值
**

class Stu(val name : String = "Jerry", val age : Int = 30) {
  println(s"my name is : ${name}, age is : ${age}")
}

注意 :
如果主constrcutor传入的参数什么修饰都没有,比如name: String,那么如果类内部的方法使用到了,则会声明为private[this] name;否则没有该field,就只能被constructor代码使用而已

11.7、内部类

class Animal {

  class Dog(val name : String) {}
  val dogs = new ArrayBuffer[Dog]()
  def getDog(name : String) = {
    new Dog(name)
  }
}

val animal = new Animal
val dog1: animal.Dog = animal.getDog("doudoud")
animal.dogs += dog1
println(animal.dogs)

11.8、object

object,相当于class的单个实例,通常在里面放一些静态的field或者method
第一次调用object的方法时,就会执行object的constructor,也就是object内部不在method中的代码(其实就是想说静态代码块);但是object不能定义接受参数的constructor

注意,object的constructor只会在其第一次被调用时执行一次,以后再次调用就不会再次执行constructor了。object通常用于作为单例模式的实现,或者放class的静态成员,比如工具方法

object Person {
  private var eyeNum = 2
  println("this Person object.")

  def apply() = new Person("xx")
  def getEyeNum = eyeNum
}

class Person(val username:String) {
  println(s"username : ${username}")
}

调用 :
val person = Person()

结果显示 :
this Person object.
username : xx

11.9、伴生对象

如果有一个class,还有一个与class同名的object,那么就称这个object是class的伴生对象,class是object的伴生类
伴生类和伴生对象必须存放在一个.scala文件之中
伴生类和伴生对象,最大的特点就在于,互相可以访问private field

object Person {
  private val eyeNum = 2
  def getEyeNum = eyeNum
}

class Person(val name:String, val age:Int) {
  def sayHello = println("my name : " + name + "; age is : " + age + "; eyeNum : " + Person.eyeNum)
}

11.10、object继承抽象类

object的功能其实和class类似,除了不能定义接受参数的constructor之外。object也可以继承抽象类,并覆盖抽象类中的方法

abstract class UserService (var token : String){
  def addUser(username : String) :Unit
}

object UserServiceImpl extends UserService ("xxxx111"){
  override def addUser(username: String) = {
    println(token + ":" + username)
  }
}

11.11、apply方法

object中非常重要的一个特殊方法,就是apply方法。通常在伴生对象中实现apply方法,并在其中实现构造伴生类的对象的功能而创建伴生类的对象时,通常不会使用new Class的方式,而是使用Class()的方式,隐式地调用伴生对象得apply方法,这样会让对象创建更加简洁

比如,Array类的伴生对象的apply方法就实现了接收可变数量的参数,并创建一个Array对象的功能
val a = Array(1, 2, 3, 4, 5)

比如,自己定义的伴生类和伴生对象

object Person {
  def apply(name : String) = new Person3(name)
}

class Person(val name : String) {
}

11.12、main方法

就如同Java中,如果要运行一个程序,必须编写一个包含main方法类一样;在scala中,如果要运行一个应用程序,那么必须有一个main方法,作为入口
scala中的main方法定义为def main(args: Array[String]),而且必须定义在object中

object HelloWorld {
  def main(args: Array[String]) {
    println("Hello World!!!")
  }
}

除了自己实现main方法之外,还可以继承App Trait,然后将需要在main方法中运行的代码,直接作为object的constructor代码;而且用args可以接受传入的参数

object HelloWorld extends App {
  if (args.length > 0) println("hello, " + args(0))
  else println("Hello World!!!")
}

11.13、用object来实现枚举功能

Scala没有直接提供类似于Java中的Enum这样的枚举特性,如果要实现枚举,则需要用object继承Enumeration类,并且调用Value方法来初始化枚举值

object Season extends Enumeration {
  val SPRING, SUMMER, AUTUMN, WINTER = Value
}

还可以通过Value传入枚举值的id和name,通过id和toString可以获取; 还可以通过id和name来查找枚举值
object Season extends Enumeration {
val SPRING = Value(0, "spring")
val SUMMER = Value(1, "summer")
val AUTUMN = Value(2, "autumn")
val WINTER = Value(3, "winter")
}

Season(0)
Season.withName("spring")

使用枚举object.values可以遍历枚举值
for (ele <- Season.values) println(ele)

11.14、继承

Scala中,让子类继承父类,与Java一样,也是使用extends关键字。继承就代表,子类可以从父类继承父类的field和method;然后子类可以在自己内部放入父类所没有,子类特有的field和method;使用继承可以有效复用代码
子类可以覆盖父类的field和method;但是如果父类用final修饰,field和method用final修饰,则该类是无法被继承的,field和method是无法被覆盖的

Scala中,如果子类要覆盖一个父类中的非抽象方法,则必须使用override关键字
override关键字可以帮助我们尽早地发现代码里的错误,比如:override修饰的父类方法的方法名我们拼写错了;比如要覆盖的父类方法的参数我们写错了;等等
此外,在子类覆盖父类方法之后,如果我们在子类中就是要调用父类那就可以使用super关键字,显式地指定要调用父类的方法

Scala中,子类可以覆盖父类的val field,而且子类的val field还可以覆盖父类的val field的getter方法;只要在子类中使用override关键字即可

class Person {
  private var name = "Tom"
  val age = 0
  def getName = name
}

class Student extends Person {
  private var score = "A"
  def getScore = score

  override val age: Int = 10
  override def getName: String = "Hi,I'm " + super.getName
}

11.15、继承isInstanceOf和asInstanceOf

如果我们创建了子类的对象,但是又将其赋予了父类类型的变量。则在后续的程序中,我们又需要将父类类型的变量转换为子类类型的变量,应该如何做?
首先,需要使用isInstanceOf判断对象是否是指定类的对象,如果是的话,则可以使用asInstanceOf将对象转换为指定类型

注意 :
如果对象是null,则isInstanceOf一定返回false,asInstanceOf一定返回null
如果没有用isInstanceOf先判断对象是否为指定类的实例,就直接用asInstanceOf转换,则可能会抛出异常

class Person
class Student extends Person
val person: Person =  new Student
val stu : Student = if (person.isInstanceOf[Student]) person.asInstanceOf[Student]
else null
println(stu.getClass)

结果 :
class com.journey.spark.scala.extend.Student

isInstanceOf只能判断出对象是否是指定类以及其子类的对象,而不能精确判断出,对象就是指定类的对象
如果要求精确地判断对象就是指定类的对象,**那么就只能使用getClass和classOf了
对象.getClass可以精确获取对象的类,classOf[类]可以精确获取类,然后使用==操作符即可判断**

class Person
class Student extends Person
val person: Person = new Student
person.isInstanceOf[Person] // true
person.getClass == classOf[Person] // false
person.getClass == classOf[Student] //true

11.16 使用模式匹配进行类型判断

使用模式匹配,功能性上来说,与isInstanceOf一样,也是判断主要是该类以及该类的子类的对象即可,不是精准判断的

class Person
class Student extends Person
val person: Person = new Student
val student: Student = new Student

person match {
  case p: Person => println("it's Person's object")
  case _  => println("unknown type")
}

不管是 match 之前是person或者student,结果都是打印it's Person's object,其实很简单,就是都是Person或者Person的子类

11.17 protected

跟java一样,scala中同样可以使用protected关键字来修饰field和method,这样在子类中就不需要super关键字,直接就可以访问field和method
还可以使用protected[this],则只能在当前子类对象中访问父类的field和method,无法通过其他子类对象访问父类的field和method

class Person {
  protected var name: String = "Tom"
  protected[this] var hobby: String = "game"
} 
class Student extends Person {
  def sayHello = println("Hello, " + name)
  def makeFriends(student: Student) {
    println("my hobby is " + hobby + ", your hobby is " + student.hobby)
  }
}

11.18 调用父类的constructor

Scala中,每个类可以有一个主constructor和任意多个辅助constructor,而每个辅助constructor的第一行都必须是调用其他辅助constructor或者是主constructor;因此子类的辅助constructor是一定不可能直接调用父类的constructor的

只能在子类的主constructor中调用父类的constructor,以下这种语法,就是通过子类的主构造函数来调用父类的构造函数

注意:
如果是父类中接收的参数,比如name和age,子类中接收时,就不要用任何val或var来修饰了,否则会认为是子类要覆盖父类的field

11.19 匿名内部类

匿名子类,也就是说,可以定义一个类的没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量。之后甚至可以将该匿名子类的对象传递给其他函数

class Person(protected val name: String) {
  def sayHello = "Hello, I'm " + name
}
val person = new Person("Tom") {
  override def sayHello = "Hi, I'm " + name
}

11.20 抽象类

如果在父类中,有某些方法无法立即实现,而需要依赖不同的子来来覆盖,重写实现自己不同的方法实现。此时可以将父类中的这些方法不给出具体的实现,只有方法签名,这种方法就是抽象方法

而一个类中如果有一个抽象方法,那么类就必须用abstract来声明为抽象类,此时抽象类是不可以实例化的
在子类中覆盖抽象类的抽象方法时,不需要使用override关键字

如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field,抽象field意味着,scala会根据自己的规则,为var或val类型的field生成对应的getter和setter方法,但是父类中是没有该field的

子类必须覆盖field,以定义自己的具体field,并且覆盖抽象field,不需要使用override关键字

abstract class Person(val name : String) {
  val nation : String
  def sayHello : Unit
}

class Student(name : String) extends Person(name) {
  override def sayHello: Unit = println("Hello " + name)
  override val nation: String = "China"
}

12、Trait

12.1、将trait作为接口使用

Scala中的Triat是一种特殊的概念,首先我们可以将Trait作为接口来使用,此时的Triat就与Java中的接口非常类似。在Triat中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可类可以使用extends关键字继承Trait

注意:
这里不是implement,而是extends,在scala中没有implement的概念,无论继承类还是Trait,统一都是extends。类继承Trait后,必须实现其中的抽象方法,实现时不需要使用override关键字。scala不支持对类进行多继承,但是支持多重继承Trait,使用with关键字即可

trait PersonTrait {
  def eat(food : String)
}

trait MakeFriendsTrait {
  def makeFriends(person : Person)
}

class Person (val name : String) extends PersonTrait with MakeFriendsTrait with Cloneable with Serializable {
  override def eat(food: String): Unit = {
    println(s"person eat ${food}")
  }

  override def makeFriends(person: Person): Unit = {
    println(s"hello my name is ${name}, your name is ${person.name}")
  }
}

12.2、在Trait中定义具体方法

Scala中的Triat可以不是只定义抽象方法,还可以定义具体方法,此时trait更像是包含了通用工具方法的东西。有一个专有的名词来形容这种情况,就是说Trait的功能混入了类

举例来说,Trait中可以包含一些很多类都通用的功能方法,比如打印日志等等,spark中就使用了Trait来定义了通用的日志打印方法

trait Logger {
  def log(message : String) = println(message)
}

class Person (val name : String) extends Logger {
  def makeFriends(person: Person): Unit = {
    println(s"hello my name is ${name}, your name is ${person.name}")
    log(s"makeFriends method is invoked with paramter Person[name=${person.name}]")
  }
}

12.3、在Trait中定义具体字段

Scala中的Triat可以定义具体field,此时继承Trait的类就自动获得了Trait中定义的field
但是这种获取field的方式与继承class是不同的:如果是继承class获取的field,实际是定义在父类中的;而继承trait获取的field,就直接被添加到了类中

trait Person {
  val eyeNum: Int = 2
}

class Student(val name: String) extends Person {
  def sayHello = println("Hi, I'm " + name + ", I have " + eyeNum + " eyes.")
}

12.4 在Trait中定义抽象字段

Scala中的Triat可以定义抽象field,而Trait中的具体方法则可以基于抽象field来编写,但是继承Trait的类,则必须覆盖抽象field,提供具体的值

trait Human {
  val msg: String
  def say(name: String) = println(msg + ", " + name)
}

class Person(val name : String) extends Human {
  override val msg: String = "hello"

  def makeFriends(person : Person) {
    say(person.name)
    println("I'm " + name + ", I want to make friends with you!")
  }
}

12.5 Trait混入

有时我们可以在创建类的对象时,指定该对象混入某个Trait,这样,就只有这个对象混入该trait的方法,而类的其他对象则没有

trait Logged {
  def log(msg : String) {}
}

trait MyLogger extends Logged {
  override def log(msg: String): Unit = {
    println("log : " + msg)
  }
}

class Person(val name : String) extends Logged {
  def say = {
    println("Hi, I'm " + name )
    log("say method is invoked.")
  }
}

调用 : 
val person1 = new Person("Tom")
person1.say

val person2 = new Person("Tom") with MyLogger
person2.say

显示 :
Hi, I'm Tom
Hi, I'm Tom
log : say method is invoked.

12.6 Trait调用链

Scala中支持让类继承多个Trait后,依次调用多个Trait中的同一个方法,只要让多个Trait的同一个方法中,在最后都执行super.方法即可

类中调用多个Trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条
这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现依赖

trait Handler {
  def handle(data : String) {}
}

trait DataValidHandler extends Handler {
  override def handle(data: String): Unit = {
    println("check data : " + data)
    super.handle(data)
  }
}

trait SignatureValidHandler extends Handler {
  override def handle(data: String): Unit = {
    println("check signature: " + data)
    super.handle(data)
  }
}

class Person(val name : String) extends SignatureValidHandler with DataValidHandler {

  def say = {
    println("Hello " + name)
    handle(name)
  }
}

12.7 混合使用trait的具体方法和抽象方法

在Trait中,可以混合使用具体方法和抽象方法。可以让具体方法依赖于抽象方法,而抽象方法则放到继承Trait的类中去实现。这种Trait其实就是设计模式中的模板设计模式的体现

trait Valid {
  def getName : String
  def valid : Boolean = {
    getName == "Tom"
  }
}

class Person(val name : String) extends Valid {
  override def getName: String = name
}

12.8 Trait的构造机制

在Scala中,Trait也是有构造代码的,也就是Trait中的,不包含在任何方法中的代码。而继承了Trait的类的构造机制如下:
1、父类的构造函数执行
2、trait的构造代码执行,多个Trait从左到右依次执行
3、构造Trait时会先构造父Trait,如果多个Trait继承同一个父Trait,则父Trait只会构造一次
4、所有Trait构造完毕之后,子类的构造函数执行

核心 : 构造是从左到右,执行是从右到左

class Person { println("Person constructor!") }
trait Logger { println("Logger constructor!") }
trait MyLogger extends Logger { println("MyLogger constructor!") }
trait TimeLogger extends Logger { println("TimeLogger constructor!") }
class Student extends Person with MyLogger with TimeLogger {
  println("Student constructor!")
}

执行结果 :
Logger constructor!
MyLogger constructor!
TimeLogger constructor!
Student constructor!

12.9、Trait继承class

在Scala中,Trait也可以继承自class,此时这个class就会成为所有继承该Trait的类的父类

class MyUtil {
  def printMessage(msg: String) = println(msg)
}

trait Logger extends MyUtil {
  def log(msg: String) = printMessage("log: " + msg)
}

class Person(val name: String) extends Logger {
  def sayHello {
    log("Hi, I'm " + name)
    printMessage("Hi, I'm " + name)
  }
}

13、函数式编程

Scala是一门既面向对象,又面向过程的语言。因此在Scala中有非常好的面向对象的特性,可以使用Scala来基于面向对象的思想开发大型复杂的系统和工程;而且Scala也面向过程,因此Scala中有函数的概念。在Scala中,函数与类、对象等一样,都是一等公民。Scala中的函数可以独立存在,不需要依赖任何类和对象。

Scala的函数式编程,就是Scala面向过程的最好的佐证。也正是因为函数式编程,才让Scala具备了Java所不具备的更强大的功能和特性

13.1、将函数赋值给变量

Scala中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量。Scala的语法规定,将函数赋值给变量时,必须在函数后面加上空格和下划线

def main(args: Array[String]): Unit = {
  // String => Unit,其实就是输入是String,无返回
  val sayHellFunc: String => Unit = sayHello _
  sayHellFunc("Tom")
}

def sayHello(name : String): Unit = {
    println("Hello, " + name)
}

13.2、匿名函数

Scala中,函数也可以不需要命名,此时函数被称为匿名函数。可以直接定义函数之后,将函数赋值给某个变量;也可以将直接定义的匿名函数传入其他函数之中
Scala定义匿名函数的语法规则就是,(参数名: 参数类型) => 函数体

val sayHelloFunc = (name : String) => println("Hello," + name)

13.3、高阶函数

Scala中,由于函数是一等公民,因此可以直接将某个函数传入其他函数。接收其他函数作为参数的函数,也被称作高阶函数(higher-order function)

def main(args: Array[String]): Unit = {
  // 这是一个输入为String,无返回只有打印的一个函数
  val sayHelloFunc = (name : String) => println("Hello, " + name)
  
  // 传入一个函数和一个String
  greeting(sayHelloFunc, "Tom")
}

// 函数的第一个参数是函数,第二个参数是一个String
def greeting(func : (String) => Unit, name : String) {
  func(name)
}

输出 : 
Hello, Tom

Array(1, 2, 3, 4, 5, 6).map((num: Int) => num * num)

// 高阶函数的另外一个个能是将函数作为返回值
def getGreetingFunc(msg : String) = (name : String) => println(msg + ";" + name)

val greetingFunc : String => Unit = getGreetingFunc("hello")
greetingFunc("Tom")

13.4、高阶函数类型推断

高阶函数可以自动推断出参数类型,而不需要写明类型;而且对于只有一个参数的函数,还可以省去其小括号;如果仅有的一个参数在右侧的函数体内只使用一次,则还可以将接收参数省略,并且将参数用_来替代

def greeting(func : (String) => Unit, name : String) {
    func(name)
}

greeting((name : String) => println("Hello," + name), "Tom")
greeting((name) => println("Hello," + name), "Tom")
greeting(name => println("Hello," + name), "Tom")

def triple(func: (Int) => Int) = { func(3) }
triple(3 * _)

13.5、Scala的常用高阶函数

// map: 对传入的每个元素都进行映射,返回一个处理后的元素
Array(1, 2, 3, 4, 5).map(2 * _)

// foreach: 对传入的每个元素都进行处理,但是没有返回值
(1 to 9).map("*" * _).foreach(println _)

// filter: 对传入的每个元素都进行条件判断,如果对元素返回true,则保留该元素,否则过滤掉该元素
(1 to 20).filter(_ % 2 == 0)

// reduceLeft: 从左侧元素开始,进行reduce操作,即先对元素1和元素2进行处理,然后将结果与元素3处理,再将结果与元素4处理,依次类推,即为reduce;reduce操作必须掌握!spark编程的重点!!!
// 下面这个操作就相当于1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9
(1 to 9).reduceLeft( _ * _)

// sortWith: 对元素进行两两相比,进行排序
Array(3, 2, 5, 4, 10, 1).sortWith(_ < _)

13.6、闭包

函数在变量不处于其有效作用域时,还能够对变量进行访问,即为闭包

def getGreetingFunc(msg : String) = (name : String) => println(msg + "," + name)

val greetingFunc = getGreetingFunc("hello")
greetingFunc("Tom")

两次调用getGreetingFunc函数,传入不同的msg,并创建不同的函数返回。然而,msg只是一个局部变量,却在getGreetingFunc执行完之后,还可以继续存在创建的函数之中;**greetingFunc("leo"),调用时,值为"hello"的msg被保留在了函数体内部,可以反复的使用
这种变量超出了其作用域,还可以使用的情况,即为闭包**

Scala通过为每个函数创建对象来实现闭包,实际上对于getGreetingFunc函数创建的函数,msg是作为函数对象的变量存在的,因此每个函数才可以拥有不同的msg

Scala编译器会确保上述闭包机制

13.7、SAM转换

在Java中,不支持直接将函数传入一个方法作为参数,通常来说,唯一的办法就是定义一个实现了某个接口的类的实例对象,该对象只有一个方法;而这些接口都只有单个的抽象方法,也就是single abstract method,简称为SAM

由于Scala是可以调用Java的代码的,因此当我们调用Java的某个方法时,可能就不得不创建SAM传递给方法,非常麻烦;但是Scala又是支持直接传递函数的。此时就可以使用Scala提供的,在调用Java方法时,使用的功能,SAM转换,即将SAM转换为Scala函数

要使用SAM转换,需要使用Scala提供的特性,隐式转换

import javax.swing._
import java.awt.event._

val button = new JButton("Click")
button.addActionListener(new ActionListener {
  override def actionPerformed(event: ActionEvent) {
    println("Click Me!!!")
  }
})

implicit def getActionListener(actionProcessFunc: (ActionEvent) => Unit) = new ActionListener {
  override def actionPerformed(event: ActionEvent) {
    actionProcessFunc(event)
  }
}
button.addActionListener((event: ActionEvent) => println("Click Me!!!"))

13.8、Currying函数

Curring函数,指的是,将原来接收两个参数的一个函数,转换为两个函数,第一个函数接收原先的第一个参数,然后返回接收原先第二个参数的第二个函数
在函数调用的过程中,就变为了两个函数连续调用的形式

def sum(a : Int, b : Int) = a + b
def sum2(a : Int) = (b : Int) => a + b
def sum3(a :Int)(b : Int) = a + b

14、集合

Scala中的集合体系主要包括:Iterable、Seq、Set、Map。其中Iterable是所有集合根Trait
Scala中的集合是分成可变和不可变两类集合的,其中可变集合就是说,集合的元素可以动态修改,而不可变集合的元素在初始化之后,就无法修改了。分别对应scala.collection.mutable和scala.collection.immutable两个包

Seq下包含了Range、ArrayBuffer、List等子trait
其中Range就代表了一个序列,通常可以使用“1 to 10”这种语法来产生一个Range ArrayBuffer就类似于Java中的ArrayList
List代表一个不可变的列表

14.1、List

List有head和tail,head代表List的第一个元素,tail代表第一个元素之后的所有元素,list.head,list.tail
List有特殊的::操作符,可以用于将head和tail合并成一个List,0 :: list
如果一个List只有一个元素,那么它的head就是这个元素,它的tail是Nil

def decorator(list : List[Int], prefix : String) {
  if (list != Nil) {
    println(prefix + list.head)
    decorator(list.tail, prefix)
  }
}

14.2、Nil/null/None/noting

Nil : 在Scal中,Nil是一个空列表,它是List[Noting]类型的一个实例,它表示一个空的集合或列表
null : null是Java中的一个关键字,表示一个空引用。在Scal中,null是一个特殊的类型,它可以被赋值给任何引用类型的变量
None : none是Option类型的一个实例,它表示一个空的Option。Option类型是Scala中的一个容器类型,它可以包含一个值或者空值
nothing : noghting是Scala中的一个特殊类型,它表示一个不存在的值。它通常用于标识一个函数永远不会反悔值,或者表示一个异常的类型

val map = Map("Japan" -> "Tokyo", "China" -> "Beijing")

map.get("France") match {
  case Some(provice) => println(provice)
  case None => println("unknow")
}

// val nameMaybe: Option[String] = None
val nameMaybe: Option[String] = Some("Tom")
nameMaybe match {
  case Some(_) => println("yes")
  case None => println("No name")
}

14.3、LinkedList

LinkedList代表一个可变的列表,使用elem可以引用其头部,使用next可以引用其尾部
val l = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5); l.elem; l.next

案例:使用while循环将LinkedList中的每个元素都乘以2
val list = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5)
var currentList = list
while (currentList != Nil) {
  currentList.elem = currentList.elem * 2
  currentList = currentList.next
}

案例:使用while循环将LinkedList中,从第一个元素开始,每隔一个元素,乘以2
val list = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
var currentList = list
var first = true
while (currentList != Nil && currentList.next != Nil) {
  if (first) { currentList.elem = currentList.elem * 2; first = false }
  currentList  = currentList.next.next
  if (currentList != Nil) currentList.elem = currentList.elem * 2
}

14.4、Set

// Set代表一个没有重复元素的集合
// 将重复元素加入Set是没有用的,比如val s = Set(1, 2, 3); s + 1; s + 4
// 而且Set是不保证插入顺序的,也就是说,Set中的元素是乱序的,val s = new scala.collection.mutable.HashSet[Int](); s += 1; s += 2; s += 5

// LinkedHashSet会用一个链表维护插入顺序,val s = new scala.collection.mutable.LinkedHashSet[Int](); i += 1; s += 2; s += 5

// SrotedSet会自动根据key来进行排序,val s = scala.collection.mutable.SortedSet("orange", "apple", "banana")

15、模式匹配

15.1、简单模式匹配

Scala是没有Java中的switch case语法的,相对应的,Scala提供了更加强大的match case语法,即模式匹配,类替代switch case,match case也被称为模式匹配
Scala的match case与Java的switch case最大的不同点在于,Java的switch case仅能匹配变量的值,比1、2、3等;而Scala的match case可以匹配各种情况,比如变量的类型、集合的元素、有值或无值
match case的语法如下:变量 match { case 值 => 代码 }。如果值为下划线,则代表了不满足以上所有情况下的默认情况如何处理。此外,match case中,只要一个case分支满足并处理了,就不会继续判断下一个case分支了。(与Java不同,java的switch case需要用break阻止)
match case语法最基本的应用,就是对变量的值进行模式匹配

def judgeGrade(grade: String) {
  grade match {
    case "A" => println("Excellent")
    case "B" => println("Good")
    case "C" => println("Just so so")
    case _ => println("you need work harder")
  }
}

15.2、在模式匹配中使用if守卫

Scala的模式匹配语法,有一个特点在于,可以在case后的条件判断中,不仅仅只是提供一个值,而是可以在值后面再加一个if守卫,进行双重过滤

def judgeGrade(name: String, grade: String) {
  grade match {
    case "A" => println(name + ", you are excellent")
    case "B" => println(name + ", you are good")
    case "C" => println(name + ", you are just so so")
    case _ if name == "Tom" => println(name + ", you are a good boy, come on")
    case _ => println("you need to work harder")
  }
}

15.3、在模式匹配中进行变量赋值

Scala的模式匹配语法,有一个特点在于,可以将模式匹配的默认情况,下划线,替换为一个变量名,此时模式匹配语法就会将要匹配的值赋值给这个变量,从而可以在后面的处理语句中使用要匹配的值

def judgeGrade(name: String, grade: String) {
  grade match {
    case "A" => println(name + ", you are excellent")
    case "B" => println(name + ", you are good")
    case "C" => println(name + ", you are just so so")
    case _grade if name == "Tom" => println(name + ", you are a good boy, come on, your grade is " + _grade)
    case _grade => println("you need to work harder, your grade is " + _grade)
  }
}

15.4、对类型进行模式匹配

Scala的模式匹配一个强大之处就在于,可以直接匹配类型,而不是值

import java.io._

def processException(e: Exception) {
  e match {
    case e1: IllegalArgumentException => println("you have illegal arguments! exception is: " + e1)
    case e2: FileNotFoundException => println("cannot find the file you need read or write!, exception is: " + e2)
    case e3: IOException => println("you got an error while you were doing IO operation! exception is: " + e3)
    case _: Exception => println("cannot know which exception you have!" )
  }
}

15.5、对Array和List进行模式匹配

对Array进行模式匹配,分别可以匹配带有指定元素的数组、带有指定个数元素的数组、以某元素打头的数组
对List进行模式匹配,与Array类似,但是需要使用List特有的::操作符

def greeting(arr: Array[String]) {
  arr match {
    case Array("Tom") => println("Hi, Tom!")
    case Array(girl1, girl2, girl3) => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
    case Array("Tom", _*) => println("Hi, Leo, please introduce your friends to me.")
    case _ => println("hey, who are you?")
  }
}

def greeting(list: List[String]) {
  list match {
    case "Tom" :: Nil => println("Hi, Tom!")
    case girl1 :: girl2 :: girl3 :: Nil => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
    case "Tom" :: tail => println("Hi, Tom, please introduce your friends to me.")
    case _ => println("hey, who are you?")
  }
}

15.6、case class与模式匹配

Scala中提供了一种特殊的类,用case class进行声明,称作样例类。case class其实有点类似于Java中的JavaBean的概念。即只定义field,并且由Scala编译时自动提供getter和setter方法,但是没有method

case class的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来)
Scala自动为case class定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回case class对象

class Person
case class Teacher(name: String, subject: String) extends Person
case class Student(name: String, classroom: String) extends Person

def judgeIdentify(person: Person) {
  person match {
    case Teacher(name, subject) => println("Teacher, name is " + name + ", subject is " + subject)
    case Student(name, classroom) => println("Student, name is " + name + ", classroom is " + classroom)
    case _ => println("Illegal access, please go out of the school!")
  }  
}

15.7、Option与模式匹配

Scala有一种特殊的类型,叫做Option。Option有两种值,一种是Some,表示有值,一种是None,表示没有值
Option通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比null来的更加简洁明了

val grades = Map("Leo" -> "A", "Jack" -> "B", "Jen" -> "C")

def getGrade(name: String) {
  val grade = grades.get(name)
  grade match {
    case Some(grade) => println("your grade is " + grade)
    case None => println("Sorry, your grade information is not in the system")
  }
}

16、类型参数

16.1、泛型类

泛型类,顾名思义,其实就是在类的声明中,定义一些泛型类型,然后在类内部,比如field或者method,就可以使用这些泛型类型
使用泛型类,通常是需要对类中的某些成员,比如某些field和method中的参数或变量,进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性
如果不使用泛型进行统一的类型限制,那么在后期程序运行过程中,难免会出现问题,比如传入了不希望的类型,导致程序出问题。
在使用类的时候,比如创建类的对象,将类型参数替换为实际的类型,即可
Scala自动推断泛型类型特性:直接给使用了泛型类型的field赋值时,Scala会自动进行类型推断

class Student[T](val schoolId: T,val stuId: T) {
  def getIdentify= "S-" + schoolId + "-" + stuId
}

val student = new Student[Int](11, 101)
student.getIdentify

16.2、泛型函数

泛型函数,与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值之间,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制
与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可以在调用函数时,手动指定泛型类型

def getCard[T](content : T) = {
  content match {
    case e : Int => "card: 001, " + e
    case e : String => "card: this is your card, " + e
    case _  => "card: " + content
  }
}

getCard[String]("xxx")

16.3、上边界Bounds

在指定泛型类型的时候,有时,我们需要对泛型类型的范围进行界定,而不是可以是任意的类型。 比如,我们可能要求某个泛型类型,它就必须是某个类的子类,这样在程序中就可以放心地调用泛型类型继承的父类的方法,程序才能正常的使用和运行。此时就上边界Bounds
Scala的上下边界特性允许泛型类型必须是某个类的子类,或者必须是某个类的父类

class Person(val name: String) {
  def sayHello = println("Hello, I'm " + name)

  def makeFriends(person: Person) {
    sayHello
    person.sayHello
  }
}

class Student(name: String) extends Person(name)
class Party[T <: Person](p1: T, p2: T) {
  def play = p1.makeFriends(p2)
}

val stu1 = new Student("zhangsan")
val stu2 = new Student("lisi")
val party = new Party[Student](stu1, stu2)
party.play

注意 : T <: Person 是可以是Person或者Person的子类

16.4、下边界Bounds

除了指定泛型类型的上边界,还可以指定下边界,即指定泛型类型必须是某个类的父类

def getIDCard[R >: Child](person: R) {
  person match {
    case _: Child => println("please tell us your parents' names.")
    case _: Father => println("sign your name for your child's id card.")
    case _ => println("sorry, you are not allowed to get id card.")
  }
}

class Father(val name: String)
class Child(name: String) extends Father(name)

注意 : R >: Child 是可以是Child的父类和自己的

16.5、View Bounds

上下边界Bounds,虽然可以让一种泛型类型,支持有父子关系的多种类型。但是,在某个类与上下边界Bounds指定的父子类型范围内的类都没有任何关系,则默认是肯定不能接受的
然而,View Bounds作为一种上下边界Bounds的加强版,支持可以对类型进行隐式转换,将指定的类型进行隐式转换后,再判断是否在边界指定的类型范围内

class Person(val name: String) {
  def sayHello = println("Hello, I'm " + name)
  def makeFriends(p: Person) {
    sayHello
    p.sayHello
  }
}
class Student(name: String) extends Person(name)
class Dog(val name: String) { def sayHello = println("Wang, Wang, I'm " + name) }

implicit def dog2person(dog: Object): Person = if(dog.isInstanceOf[Dog]) {val _dog = dog.asInstanceOf[Dog]; new Person(_dog.name) } else Nil

class Party[T <% Person](p1: T, p2: T)

16.6、Context Bounds

Context Bounds是一种特殊的Bounds,它会根据泛型类型的声明,比如“T:类型”要求必须存在一个类型为“类型[T]”的隐式值。Context Bounds之所以叫Context,是因为它基于的是一种全局的上下文,需要使用到上下文中的隐式值以及注入

class Calculator[T: Ordering] (val number1: T, val number2: T) {
  def max(implicit order: Ordering[T]) = if(order.compare(number1, number2) > 0) number1 else number2
}

16.7、Manifest Context Bounds

在Scala中,如果要实例化一个泛型数组,就必须使用Manifest Context Bounds。也就是说,如果数组元素类型为T的话,需要为类或者函数定义[T: Manifest]泛型类型,这样才能实例化Array[T]这种泛型数组

class Meat(val name: String)
class Vegetable(val name: String)

def packageFood[T: Manifest] (food: T*) = {
  val foodPackage = new Array[T](food.length)
  for(i <- 0 until food.length) foodPackage(i) = food(i)
  foodPackage 
}

16.8、协变和逆变

Scala的协变和逆变是非常有特色的!完全解决了Java中的泛型的一大缺憾!
举例来说,Java中,如果有Professional是Master的子类,那么Card[Professionnal]是不是Card[Master]的子类?答案是:不是。因此对于开发程序造成了很多的麻烦。
而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题

class Master
class Professional extends Master

// 大师以及大师级别以下的名片都可以进入会场
class Card[+T] (val name: String)
def enterMeet(card: Card[Master]) {
  println("welcome to have this meeting!")
}

// 只要专家级别的名片就可以进入会场,如果大师级别的过来了,当然可以了!
class Card[-T] (val name: String)
def enterMeet(card: Card[Professional]) {
  println("welcome to have this meeting!")
}

16.9、Existential Type

在Scala里,有一种特殊的类型参数,就是Existential Type,存在性类型
Array[T] forSome { type T }
Array[_] 表示某一种存在的类型,其实就是一个占位符

17、隐式转换

17.1、隐式转换基本原理

要实现隐式转换,只要程序可见的范围内定义隐式转换函数即可。Scala会自动使用隐式转换函数隐式转换函数与普通函数唯一的语法区别就是,要以implicit开头,而且最好要定义函数返回类型

class SpecialPerson(val name: String)
class Student(val name: String)
class Older(val name: String)

// 其实就是可以将Student、Older转换为SpecialPerson
implicit def object2SpecialPerson(obj: Object): SpecialPerson = {
  if (obj.getClass == classOf[Student]) {
    val stu = obj.asInstanceOf[Student]; new SpecialPerson(stu.name)
  }
  else if (obj.getClass == classOf[Older]) {
    val older = obj.asInstanceOf[Older]; new SpecialPerson(older.name)
  }
  else null
}

var ticketNumber = 0

def buySpecialTicket(p: SpecialPerson) = {
ticketNumber += 1
"T-" + ticketNumber
}

调用 :
val stu = new Student("Student")
println(buySpecialTicket(stu))

val older = new Student("Older")
println(buySpecialTicket(older))

val specialPerson = new SpecialPerson("SpecialPerson")
println(buySpecialTicket(specialPerson))

17.2、使用隐式转换加强现有类型

隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能。也就是说,可以为某个类定义一个加强版的类,并定义互相之间的隐式转换,从而让源类在使用加强版的方法时,由Scala自动进行隐式转换为加强类,然后再调用该方法

class Man(val name: String)
class Superman(val name: String) {
  def emitLaser = println("emit a laster!")
}

implicit def man2superman(man: Man): Superman = new Superman(man.name)

val tom = new Man("Tom")
tom.emitLaser

17.3、隐式转换函数作用域与导入

Scala默认会使用两种隐式转换,一种是源类型,或者目标类型的伴生对象内的隐式转换函数;一种是当前程序作用域内的可以用唯一标识符表示的隐式转换函数

如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import语法引入某个包下的隐式转换函数,比如import test._。通常建议,仅仅在需要进行隐式转换的地方,比如某个函数或者方法内,用import导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换

17.4、隐式参数

所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用implicit修饰的对象,即隐式值,并注入参数
Scala会在两个范围内查找:一种是当前作用域内可见的val或var定义的隐式变量;一种是隐式参数类型的伴生对象内的隐式值

class SignPen {
    def write(content: String) = println(content)
}

implicit val signPen = new SignPen

def signForExam(name: String)(implicit signPen: SignPen) {
    signPen.write(name + " come to exam in time.")
}

调用 : 
val signPen = new SignPen
signForExam("xxx")(signPen)

你可能感兴趣的:(scala)