动态

详情 返回 返回

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)

如感興趣,點贊加關注,謝謝!!!

user avatar u_16297326 头像 debuginn 头像 tech 头像 jkdataapi 头像 changqingdezi 头像 swifter 头像 javadog 头像 lvxingdefanka 头像 wangjingyu_5f58472234cff 头像 sorra 头像
点赞 10 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.