Scala語(yǔ)言學(xué)習(xí)五 (高級(jí)用法)

Scala 高級(jí)用法

樣例類

? 樣例類是一種特殊類,它可以用來(lái)快速定義一個(gè)用于保存數(shù)據(jù)的類(類似于Java POJO類)氓润,在后續(xù)要學(xué)習(xí)并發(fā)編程和spark、flink這些框架也都會(huì)經(jīng)常使用它牺丙。

語(yǔ)法

ase class 樣例類名([var/val] 成員變量名1:類型1, 成員變量名2:類型2, 成員變量名3:類型3)    

示例:

需求

  • 定義一個(gè)Person樣例類午绳,包含姓名和年齡成員變量
  • 創(chuàng)建樣例類的對(duì)象實(shí)例("張三"、20)痒谴,并打印它
object _01CaseClassDemo {
  case class Person(name:String, age:Int)

  def main(args: Array[String]): Unit = {
    val zhangsan = Person("張三", 20)

    println(zhangsan)
  }
}

需求

  • 定義一個(gè)Person樣例類衰伯,包含姓名和年齡成員變量
  • 創(chuàng)建樣例類的對(duì)象實(shí)例("張三"、20)
  • 修改張三的年齡為23歲积蔚,并打印
object _02CaseClassDemo {
  case class Person(var name:String, var age:Int)

  def main(args: Array[String]): Unit = {
    val zhangsan = Person("張三", 20)

    zhangsan.age = 23

    println(zhangsan)
  }
}

樣例類的方法

當(dāng)我們定義一個(gè)樣例類,編譯器自動(dòng)幫助我們實(shí)現(xiàn)了以下幾個(gè)有用的方法:

  • apply方法
  • toString方法
  • equals方法
  • hashCode方法
  • copy方法

apply方法

apply方法可以讓我們快速地使用類名來(lái)創(chuàng)建對(duì)象烦周。參考以下代碼:

case class CasePerson(name:String, age:Int)

object CaseClassDemo {
  def main(args: Array[String]): Unit = {
    val lisi = CasePerson("李四", 21)
    println(lisi.toString)
  }
}

默認(rèn)就是用apply方法

toString方法

toString返回樣例類名稱(成員變量1, 成員變量2, 成員變量3....)尽爆,我們可以更方面查看樣例類的成員

case class CasePerson(name:String, age:Int)

object CaseClassDemo {
  def main(args: Array[String]): Unit = {
    val lisi = CasePerson("李四", 21)
    println(lisi.toString)
    // 輸出:CasePerson(李四,21)
  }
}

默認(rèn)也可以不用寫toString,直接打印類名直接就是toString的功能

equals方法

樣例類自動(dòng)實(shí)現(xiàn)了equals方法读慎,可以直接使用==比較兩個(gè)樣例類是否相等漱贱,即所有的成員變量是否相等

示例

  • 創(chuàng)建一個(gè)樣例類Person,包含姓名夭委、年齡
  • 創(chuàng)建名字年齡分別為"李四", 21的兩個(gè)對(duì)象
  • 比較它們是否相等
val lisi1 = CasePerson("李四", 21)
val lisi2 = CasePerson("李四", 21)
println(lisi1 == lisi2)
// 輸出:true

hashCode方法

樣例類自動(dòng)實(shí)現(xiàn)了hashCode方法幅狮,如果所有成員變量的值相同,則hash值相同株灸,只要有一個(gè)不一樣崇摄,則hash值不一樣。

示例

  • 創(chuàng)建名字年齡分別為"李四", 21的對(duì)象
  • 再創(chuàng)建一個(gè)名字年齡分別為"李四", 22的對(duì)象
  • 分別打印這兩個(gè)對(duì)象的哈希值
val lisi1 = CasePerson("李四", 21)
val lisi2 = CasePerson("李四", 22)

println(lisi1.hashCode())
println(lisi2.hashCode())

copy方法

樣例類實(shí)現(xiàn)了copy方法慌烧,可以快速創(chuàng)建一個(gè)相同的實(shí)例對(duì)象逐抑,可以使用帶名參數(shù)指定給成員進(jìn)行重新賦值

示例

  • 創(chuàng)建名字年齡分別為"李四", 21的對(duì)象
  • 通過(guò)copy拷貝,名字為"王五"的對(duì)象
**示例**

- 創(chuàng)建名字年齡分別為"李四", 21的對(duì)象
- 通過(guò)copy拷貝屹蚊,名字為"王五"的對(duì)象

樣例對(duì)象

語(yǔ)法

case object 樣例對(duì)象名

示例

需求說(shuō)明

  • 定義一個(gè)性別Sex枚舉厕氨,它只有兩個(gè)實(shí)例(男性——Male进每、女性——Female)
  • 創(chuàng)建一個(gè)Person類,它有兩個(gè)成員(姓名命斧、性別)
  • 創(chuàng)建兩個(gè)Person對(duì)象("張三"田晚、男性)、("李四"国葬、"女")
trait Sex /*定義一個(gè)性別特質(zhì)*/
case object Male extends Sex        // 定義一個(gè)樣例對(duì)象并實(shí)現(xiàn)了Sex特質(zhì)
case object Female extends Sex      

case class Person(name:String, sex:Sex)

object CaseClassDemo {
  def main(args: Array[String]): Unit = {
    val zhangsan = Person("張三", Male)

    println(zhangsan)
  }
}

模式匹配

簡(jiǎn)單匹配模式

在Java中贤徒,有switch關(guān)鍵字,可以簡(jiǎn)化if條件判斷語(yǔ)句胃惜。在scala中泞莉,可以使用match表達(dá)式替代。

語(yǔ)法

變量 match {
    case "常量1" => 表達(dá)式1
    case "常量2" => 表達(dá)式2
    case "常量3" => 表達(dá)式3
    case _ => 表達(dá)式4      // 默認(rèn)匹配
}

示例

需求說(shuō)明

  1. 從控制臺(tái)輸入一個(gè)單詞(使用StdIn.readLine方法)
  2. 判斷該單詞是否能夠匹配以下單詞船殉,如果能匹配鲫趁,返回一句話
  3. 打印這句話
println("請(qǐng)輸出一個(gè)詞:")
// StdIn.readLine表示從控制臺(tái)讀取一行文本
val name = StdIn.readLine()

val result = name match {
    case "hadoop" => "大數(shù)據(jù)分布式存儲(chǔ)和計(jì)算框架"
    case "zookeeper" => "大數(shù)據(jù)分布式協(xié)調(diào)服務(wù)框架"
    case "spark" => "大數(shù)據(jù)分布式內(nèi)存計(jì)算框架"
    case _ => "未匹配"
}

println(result)

匹配類型

語(yǔ)法

變量 match {
    case 類型1變量名: 類型1 => 表達(dá)式1
    case 類型2變量名: 類型2 => 表達(dá)式2
    case 類型3變量名: 類型3 => 表達(dá)式3
    ...
    case _ => 表達(dá)式4
}

示例

需求說(shuō)明

  • 定義一個(gè)變量為Any類型,然后分別給其賦值為"hadoop"利虫、1挨厚、1.0
  • 定義模式匹配,然后分別打印類型的名稱
val a:Any = "hadoop"

val result = a match {
    case _:String => "String"
    case _:Int => "Int"
    case _:Double => "Double"
}

println(result)

守衛(wèi)

就是java中case方法里的default糠惫,也就是說(shuō)在沒(méi)有匹配到值得情況下得取一個(gè)默認(rèn)的值

示例

需求說(shuō)明

  • 從控制臺(tái)讀入一個(gè)數(shù)字a(使用StdIn.readInt)
  • 如果 a >= 0 而且 a <= 3疫剃,打印[0-3]
  • 如果 a >= 4 而且 a <= 8,打印[3,8]
  • 否則硼讽,打印未匹配
val a = StdIn.readInt()

a match {
    case _ if a >= 0 && a <= 3 => println("[0-3]")
    case _ if a >= 4 && a <= 8 => println("[3-8]")
    case _ => println("未匹配")
}

匹配樣例類

scala可以使用模式匹配來(lái)匹配樣例類巢价,從而可以快速獲取樣例類中的成員數(shù)據(jù)。后續(xù)固阁,我們?cè)陂_發(fā)Akka案例時(shí)壤躲,還會(huì)用到。

需求說(shuō)明

  • 創(chuàng)建兩個(gè)樣例類Customer备燃、Order
    • Customer包含姓名碉克、年齡字段
    • Order包含id字段
  • 分別定義兩個(gè)案例類的對(duì)象,并指定為Any類型
  • 使用模式匹配這兩個(gè)對(duì)象并齐,并分別打印它們的成員變量值
/ 1. 創(chuàng)建兩個(gè)樣例類
case class Person(name:String, age:Int)
case class Order(id:String)

def main(args: Array[String]): Unit = {
    // 2. 創(chuàng)建樣例類對(duì)象漏麦,并賦值為Any類型
    val zhangsan:Any = Person("張三", 20)
    val order1:Any = Order("001")

    // 3. 使用match...case表達(dá)式來(lái)進(jìn)行模式匹配
    // 獲取樣例類中成員變量
    order1 match {
        case Person(name, age) => println(s"姓名:${name} 年齡:${age}")
        case Order(id1) => println(s"ID為:${id1}")
        case _ => println("未匹配")
    }
}

匹配集合,列表况褪,元祖

匹配集合

scala中的模式匹配撕贞,還能用來(lái)匹配集合。

示例

  • 依次修改代碼定義以下三個(gè)數(shù)組

    Array(1,x,y)   // 以1開頭窝剖,后續(xù)的兩個(gè)元素不固定
    Array(0)     // 只匹配一個(gè)0元素的元素
    Array(0, ...)  // 可以任意數(shù)量麻掸,但是以0開頭
    
  • 使用模式匹配上述數(shù)組

val arr = Array(1, 3, 5)
arr match {
    case Array(1, x, y) => println(x + " " + y)
    case Array(0) => println("only 0")
    case Array(0, _*) => println("0 ...")
    case _ => println("something else")
}

匹配列表

示例

  • 依次修改代碼定義以下三個(gè)列表

    List(0)               // 只保存0一個(gè)元素的列表
    List(0,...)           // 以0開頭的列表,數(shù)量不固定
    List(x,y)         // 只包含兩個(gè)元素的列表  
    
  • 使用模式匹配上述列表

val list = List(0, 1, 2)

list match {
    case 0 :: Nil => println("只有0的列表")
    case 0 :: tail => println("0開頭的列表")
    case x :: y :: Nil => println(s"只有另兩個(gè)元素${x}, ${y}的列表")
    case _ => println("未匹配")
}

匹配元祖

  • 依次修改代碼定義以下兩個(gè)元組

    (1, x, y)     // 以1開頭的赐纱、一共三個(gè)元素的元組
    (x, y, 5)   // 一共有三個(gè)元素脊奋,最后一個(gè)元素為5的元組
    
  • 使用模式匹配上述元素

val tuple = (2, 2, 5)

tuple match {
    case (1, x, y) => println(s"三個(gè)元素熬北,1開頭的元組:1, ${x}, ${y}")
    case (x, y, 5) => println(s"三個(gè)元素,5結(jié)尾的元組:${x}, ${y}, 5")
    case _ => println("未匹配")
}

Option 類型

使用Option類型诚隙,可以用來(lái)有效避免空引用(null)異常讶隐。也就是說(shuō),將來(lái)我們返回某些數(shù)據(jù)時(shí)久又,可以返回一個(gè)Option類型來(lái)替代巫延。

  • Some(x):表示實(shí)際的值

  • None:表示沒(méi)有值

  • 使用getOrElse方法,當(dāng)值為None是可以指定一個(gè)默認(rèn)值

示例說(shuō)明

  • 定義一個(gè)兩個(gè)數(shù)相除的方法地消,使用Option類型來(lái)封裝結(jié)果
  • 然后使用模式匹配來(lái)打印結(jié)果
    • 不是除零炉峰,打印結(jié)果
    • 除零打印異常錯(cuò)誤
  /**
    * 定義除法操作
    * @param a 參數(shù)1
    * @param b 參數(shù)2
    * @return Option包裝Double類型
    */
  def dvi(a:Double, b:Double):Option[Double] = {
    if(b != 0) {
      Some(a / b)
    }
    else {
      None
    }
  }

  def main(args: Array[String]): Unit = {
    val result1 = dvi(1.0, 5)

    result1 match {
      case Some(x) => println(x)
      case None => println("除零異常")
    }
  } 

示例說(shuō)明

  • 重寫上述案例,使用getOrElse方法脉执,當(dāng)除零時(shí)疼阔,或者默認(rèn)值為0
def dvi(a:Double, b:Double) = {
    if(b != 0) {
        Some(a / b)
    }
    else {
        None
    }
}

def main(args: Array[String]): Unit = {
    val result = dvi(1, 0).getOrElse(0)

    println(result)
}

偏函數(shù)

偏函數(shù)可以提供了簡(jiǎn)潔的語(yǔ)法,可以簡(jiǎn)化函數(shù)的定義半夷。配合集合的函數(shù)式編程婆廊,可以讓代碼更加優(yōu)雅。

定義

  • 偏函數(shù)被包在花括號(hào)內(nèi)沒(méi)有match的一組case語(yǔ)句是一個(gè)偏函數(shù)

  • 偏函數(shù)是PartialFunction[A, B]的一個(gè)實(shí)例

    • A代表輸入?yún)?shù)類型
    • B代表返回結(jié)果類型

示例說(shuō)明*

定義一個(gè)偏函數(shù)巫橄,根據(jù)以下方式返回

1->一

2->二

3->三

其他 —> 其他

// func1是一個(gè)輸入?yún)?shù)為Int類型淘邻,返回值為String類型的偏函數(shù)
val func1: PartialFunction[Int, String] = {
    case 1 => "一"
    case 2 => "二"
    case 3 => "三"
    case _ => "其他"
}

println(func1(2))

示例說(shuō)明

  • 定義一個(gè)列表,包含1-10的數(shù)字
  • 請(qǐng)將1-3的數(shù)字都轉(zhuǎn)換為[1-3]
  • 請(qǐng)將4-8的數(shù)字都轉(zhuǎn)換為[4-8]
  • 將其他的數(shù)字轉(zhuǎn)換為(8-*]
val list = (1 to 10).toList

val list2 = list.map{
    case x if x >= 1 && x <= 3 => "[1-3]"
    case x if x >= 4 && x <= 8 => "[4-8]"
    case x if x > 8 => "(8-*]"
}

println(list2)

正則表達(dá)式

Regex類

  • scala中提供了Regex類來(lái)定義正則表達(dá)式

  • 要構(gòu)造一個(gè)RegEx對(duì)象湘换,直接使用String類的r方法即可

  • 建議使用三個(gè)雙引號(hào)來(lái)表示正則表達(dá)式宾舅,不然就得對(duì)正則中的反斜杠來(lái)進(jìn)行轉(zhuǎn)義

val regEx = """正則表達(dá)式""".r

findAllMatchIn方法

使用findAllMatchIn方法可以獲取到所有正則匹配到的字符串

示例說(shuō)明

  • 定義一個(gè)正則表達(dá)式,來(lái)匹配郵箱是否合法
  • 合法郵箱測(cè)試:qq12344@163.com
  • 不合法郵箱測(cè)試:qq12344@.com
val r = """.+@.+\..+""".r

val eml1 = "qq12344@163.com"
val eml2 = "qq12344@.com"

if(r.findAllMatchIn(eml1).size > 0) {
    println(eml1 + "郵箱合法")
}
else {
    println(eml1 + "郵箱不合法")
}

if(r.findAllMatchIn(eml2).size > 0) {
    println(eml2 + "郵箱合法")
}
else {
    println(eml2 + "郵箱不合法")
}

示例說(shuō)明

找出以下列表中的所有不合法的郵箱

"38123845@qq.com", "a1da88123f@gmail.com", "zhansan@163.com", "123afadff.com"
val emlList =
List("38123845@qq.com", "a1da88123f@gmail.com", "zhansan@163.com", "123afadff.com")

val regex = """.+@.+\..+""".r

val invalidEmlList = emlList.filter {
    x =>
    if (regex.findAllMatchIn(x).size < 1) true else false
}

println(invalidEmlList)

異常處理

下面一段代碼

def main(args: Array[String]): Unit = {
   val i = 10 / 0
    
    println("你好彩倚!")
  }

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at ForDemo$.main(ForDemo.scala:3)
    at ForDemo.main(ForDemo.scala)

執(zhí)行程序贴浙,可以看到scala拋出了異常,而且沒(méi)有打印出來(lái)"你好"署恍。說(shuō)明程序出現(xiàn)錯(cuò)誤后就終止了。

那怎么解決該問(wèn)題呢蜻直?

在scala中盯质,可以使用異常處理來(lái)解決這個(gè)問(wèn)題

語(yǔ)法

try {
    // 代碼
}
catch {
    case ex:異常類型1 => // 代碼
    case ex:異常類型2 => // 代碼
}
finally {
    // 代碼
}
  • try中的代碼是我們編寫的業(yè)務(wù)處理代碼
  • 在catch中表示當(dāng)出現(xiàn)某個(gè)異常時(shí),需要執(zhí)行的代碼
  • 在finally中概而,是不管是否出現(xiàn)異常都會(huì)執(zhí)行的代碼

示例說(shuō)明

  • 使用try..catch來(lái)捕獲除零異常
try {
    val i = 10 / 0

    println("你好呼巷!")
} catch {
    case ex: Exception => println(ex.getMessage)
} 

拋出異常

我們也可以在一個(gè)方法中,拋出異常赎瑰。語(yǔ)法格式和Java類似王悍,使用throw new Exception...

示例說(shuō)明

  • 在main方法中拋出一個(gè)異常
  def main(args: Array[String]): Unit = {
    throw new Exception("這是一個(gè)異常")
  }

Exception in thread "main" java.lang.Exception: 這是一個(gè)異常
    at ForDemo$.main(ForDemo.scala:3)
    at ForDemo.main(ForDemo.scala)

提取器

之前我們學(xué)習(xí)過(guò)了,實(shí)現(xiàn)一個(gè)類的伴生對(duì)象中的apply方法餐曼,可以用類名來(lái)快速構(gòu)建一個(gè)對(duì)象压储。伴生對(duì)象中鲜漩,還有一個(gè)unapply方法。與apply相反集惋,unapply是將該類的對(duì)象孕似,拆解為一個(gè)個(gè)的元素。

1552639637165
1552639674932

語(yǔ)法

def unapply(stu:Student):Option[(類型1, 類型2, 類型3...)] = {
    if(stu != null) {
        Some((變量1, 變量2, 變量3...))
    }
    else {
        None
    }
}

示例說(shuō)明

  • 創(chuàng)建一個(gè)Student類刮刑,包含姓名年齡兩個(gè)字段
  • 實(shí)現(xiàn)一個(gè)類的解構(gòu)器喉祭,并使用match表達(dá)式進(jìn)行模式匹配,提取類中的字段雷绢。
class Student(var name:String, var age:Int)

object Student {
    def apply(name:String, age:Int) = {
        new Student(name, age)
    }

    def unapply(student:Student) = {
        val tuple = (student.name, student.age)

        Some(tuple)
    }
}

def main(args: Array[String]): Unit = {
    val zhangsan = Student("張三", 20)

    zhangsan match {
        case Student(name, age) => println(s"${name} => ${age}")
    }
}

泛型

scala和Java一樣泛烙,類和特質(zhì)、方法都可以支持泛型翘紊。我們?cè)趯W(xué)習(xí)集合的時(shí)候蔽氨,一般都會(huì)涉及到泛型。

scala> val list1:List[String] = List("1", "2", "3")
list1: List[String] = List(1, 2, 3)

語(yǔ)法

def 方法名[泛型名稱](..) = {
    //...
}

不考慮泛型

ef getMiddle(arr:Array[Int]) = arr(arr.length / 2)

  def main(args: Array[String]): Unit = {
    val arr1 = Array(1,2,3,4,5)

    println(getMiddle(arr1))
  }

加入泛型之后

def getMiddleElement[T](array:Array[T]) =
array(array.length / 2)

def main(args: Array[String]): Unit = {
    println(getMiddleElement(Array(1, 2, 3, 4, 5)))
    println(getMiddleElement(Array("a", "b", "c", "d", "e")))
}

泛型類

scala的類也可以定義泛型霞溪。接下來(lái)孵滞,我們來(lái)學(xué)習(xí)如何定義scala的泛型類

語(yǔ)法

class 類[T](val 變量名: T)

示例說(shuō)明

  • 實(shí)現(xiàn)一個(gè)Pair泛型類
  • Pair類包含兩個(gè)字段,而且兩個(gè)字段的類型不固定
  • 創(chuàng)建不同類型泛型類對(duì)象鸯匹,并打印
case class Pair[T](var a:T, var b:T)

def main(args: Array[String]): Unit = {
    val pairList = List(
        Pair("Hadoop", "Storm"),
        Pair("Hadoop", 2008),
        Pair(1.0, 2.0),
        Pair("Hadoop", Some(1.9))
    )

    println(pairList)
}

泛型的上下界

我們?cè)诙x方法/類的泛型時(shí)坊饶,限定必須從哪個(gè)類繼承、或者必須是哪個(gè)類的父類殴蓬。此時(shí)匿级,就需要使用到上下界。

上界定義

使用<: 類型名表示給類型添加一個(gè)上界染厅,表示泛型參數(shù)必須要從該類(或本身)繼承

[T <: 類型]

示例說(shuō)明

  • 定義一個(gè)Person類
  • 定義一個(gè)Student類痘绎,繼承Person類
  • 定義一個(gè)demo泛型方法,該方法接收一個(gè)Array參數(shù)肖粮,
  • 限定demo方法的Array元素類型只能是Person或者Person的子類
  • 測(cè)試調(diào)用demo孤页,傳入不同元素類型的Array
class Person
class Student extends Person

def demo[T <: Person](a:Array[T]) = println(a)

def main(args: Array[String]): Unit = {
    demo(Array(new Person))
    demo(Array(new Student))
    // 編譯出錯(cuò),必須是Person的子類
    // demo(Array("hadoop"))
}

下界定義

上界是要求必須是某個(gè)類的子類涩馆,或者必須從某個(gè)類繼承行施,而下界是必須是某個(gè)類的父類(或本身)

[T >: 類型]

示例說(shuō)明

  • 定義一個(gè)Person類
  • 定義一個(gè)Policeman類,繼承Person類
  • 定義一個(gè)Superman類魂那,繼承Policeman類
  • 定義一個(gè)demo泛型方法蛾号,該方法接收一個(gè)Array參數(shù),
  • 限定demo方法的Array元素類型只能是Person涯雅、Policeman
  • 測(cè)試調(diào)用demo鲜结,傳入不同元素類型的Array
class Person
class Policeman extends Person
class Superman extends Policeman

def demo[T >: Policeman](array:Array[T]) = println(array)

def main(args: Array[String]): Unit = {
    demo(Array(new Person))
    demo(Array(new Policeman))
    // 編譯出錯(cuò):Superman是Policeman的子類
    // demo(Array(new Superman))
}

協(xié)變、逆變、非變 (掌握)

spark的源代碼中大量使用到了協(xié)變精刷、逆變拗胜、非變,學(xué)習(xí)該知識(shí)點(diǎn)對(duì)我們將來(lái)閱讀spark源代碼很有幫助贬养。

class Pair[T]

object Pair {
  def main(args: Array[String]): Unit = {
    val p1 = Pair("hello")
    // 編譯報(bào)錯(cuò)挤土,無(wú)法將p1轉(zhuǎn)換為p2
    val p2:Pair[AnyRef] = p1

    println(p2)
  }
}
1558064807949

非變

class Pair[T]{}
  • 默認(rèn)泛型類是非變的
  • 類型B是A的子類型,Pair[A]和Pair[B]沒(méi)有任何從屬關(guān)系
  • Java是一樣的

協(xié)變

class Pair[+T]
  • 類型B是A的子類型误算,Pair[B]可以認(rèn)為是Pair[A]的子類型
  • 參數(shù)化類型的方向和類型的方向是一致的仰美。

逆變

class Pair[-T]
  • 類型B是A的子類型,Pair[A]反過(guò)來(lái)可以認(rèn)為是Pair[B]的子類型
  • 參數(shù)化類型的方向和類型的方向是相反的

示例

  • 定義一個(gè)Super類儿礼、以及一個(gè)Sub類繼承自Super類
  • 使用協(xié)變咖杂、逆變、非變分別定義三個(gè)泛型類
  • 分別創(chuàng)建泛型類來(lái)演示協(xié)變蚊夫、逆變诉字、非變
class Super
class Sub extends Super

class Temp1[T]
class Temp2[+T]
class Temp3[-T]

def main(args: Array[String]): Unit = {
    val a:Temp1[Sub] = new Temp1[Sub]
    // 編譯報(bào)錯(cuò)
    // 非變
    //val b:Temp1[Super] = a

    // 協(xié)變
    val c: Temp2[Sub] = new Temp2[Sub]
    val d: Temp2[Super] = c

    // 逆變
    val e: Temp3[Super] = new Temp3[Super]
    val f: Temp3[Sub] = e
}

Actor 介紹

Actor并發(fā)編程模型,是scala提供給程序員的一種與Java并發(fā)編程完全不一樣的并發(fā)編程模型知纷,是一種基于事件模型的并發(fā)機(jī)制壤圃。Actor并發(fā)編程模型是一種不共享數(shù)據(jù),依賴消息傳遞的一種并發(fā)編程模式琅轧,有效避免資源爭(zhēng)奪伍绳、死鎖等情況。

1552787528554

創(chuàng)建Actor

創(chuàng)建Actor的方式和Java中創(chuàng)建線程很類似乍桂,也是通過(guò)繼承來(lái)創(chuàng)建冲杀。

  1. 定義class或object繼承Actor特質(zhì)
  2. 重寫act方法
  3. 調(diào)用Actor的start方法執(zhí)行Actor

示例說(shuō)明

創(chuàng)建兩個(gè)Actor,一個(gè)Actor打印1-10睹酌,另一個(gè)Actor打印11-20

  • 使用class繼承Actor創(chuàng)建(如果需要在程序中創(chuàng)建多個(gè)相同的Actor)
  • 使用object繼承Actor創(chuàng)建(如果在程序中只創(chuàng)建一個(gè)Actor)
object _05ActorDemo {
  class Actor1 extends Actor {
    override def act(): Unit = (1 to 10).foreach(println(_))
  }

  class Actor2 extends Actor {
    override def act(): Unit = (11 to 20).foreach(println(_))
  }

  def main(args: Array[String]): Unit = {
    new Actor1().start()
    new Actor2().start()
  }
}

使用object繼承Actor創(chuàng)建

  object Actor1 extends Actor {
    override def act(): Unit =
      for(i <- 1 to 10) {
        println(i)
      }
  }

  object Actor2 extends Actor {
    override def act(): Unit =
      for(i <- 11 to 20) {
        println(i)
      }
  }

  def main(args: Array[String]): Unit = {
    Actor1.start()
    Actor2.start()
  }

Actor程序運(yùn)行流程

  1. 調(diào)用start()方法啟動(dòng)Actor
  2. 自動(dòng)執(zhí)行act()方法
  3. 向Actor發(fā)送消息
  4. act方法執(zhí)行完成后权谁,程序會(huì)調(diào)用exit()方法

Actor 發(fā)送接收消息

我們之前介紹Actor的時(shí)候,說(shuō)過(guò)Actor是基于事件(消息)的并發(fā)編程模型憋沿,那么Actor是如何發(fā)送消息和接收消息的呢旺芽?

發(fā)送異步消息辐啄,沒(méi)有返回值
!? 發(fā)送同步消息甥绿,等待返回值
!! 發(fā)送異步消息,返回值是Future[Any]

要給actor1發(fā)送一個(gè)異步字符串消息则披,使用以下代碼:

ctor1 ! "你好!"

接收消息

Actor中使用receive方法來(lái)接收消息,需要給receive方法傳入一個(gè)偏函數(shù)

{
    case 變量名1:消息類型1 => 業(yè)務(wù)處理1,
    case 變量名2:消息類型2 => 業(yè)務(wù)處理2,
    ...
}

示例說(shuō)明

  • 創(chuàng)建兩個(gè)Actor(ActorSender洗出、ActorReceiver)
  • ActorSender發(fā)送一個(gè)異步字符串消息給ActorReceiver
  • ActorReceive接收到該消息后士复,打印出來(lái)
1552791021244
  object ActorSender extends Actor {
    override def act(): Unit = {
      // 發(fā)送消息
      while(true) {
        ActorReceiver ! "hello!"
        TimeUnit.SECONDS.sleep(3)
      }
    }
  }

  object ActorReceiver extends Actor {
    override def act(): Unit = {
      // 持續(xù)接收消息
      while(true) {
        receive {
          case msg:String => println("接收到消息:" + msg)
        }
      }
    }
  }

  def main(args: Array[String]): Unit = {
    ActorReceiver.start()
    ActorSender.start()
  }

Actor 的Loop

上述代碼,使用while循環(huán)來(lái)不斷接收消息。

  • 如果當(dāng)前Actor沒(méi)有接收到消息阱洪,線程就會(huì)處于阻塞狀態(tài)
  • 如果有很多的Actor便贵,就有可能會(huì)導(dǎo)致很多線程都是處于阻塞狀態(tài)
  • 每次有新的消息來(lái)時(shí),重新創(chuàng)建線程來(lái)處理
  • 頻繁的線程創(chuàng)建冗荸、銷毀和切換承璃,會(huì)影響運(yùn)行效率

在scala中,可以使用loop + react來(lái)復(fù)用線程蚌本。比while + receive更高效

用Loop改寫上述代碼

// 持續(xù)接收消息
loop {
    react {
        case msg:String => println("接收到消息:" + msg)
    }
}

Actor 舉例說(shuō)明

示例一

示例說(shuō)明

  • 創(chuàng)建一個(gè)MsgActor盔粹,并向它發(fā)送一個(gè)同步消息,該消息包含兩個(gè)字段(id程癌、message)
  • MsgActor回復(fù)一個(gè)消息舷嗡,該消息包含兩個(gè)字段(message、name)
  • 打印回復(fù)消息
  case class Message(id:Int, msg:String)
  case class ReplyMessage(msg:String, name:String)

  object MsgActor extends Actor {
    override def act(): Unit = {
      loop {
        react {
          case Message(id, msg) => {
            println(s"接收到消息:${id}/${msg}")
            sender ! ReplyMessage("不太好", "Tom")
          }
        }
      }
    }
  }

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

    val replyMessage: Any = MsgActor !? Message(1, "你好")
    println("回復(fù)消息:" + replyMessage.asInstanceOf[ReplyMessage])
  }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嵌莉,一起剝皮案震驚了整個(gè)濱河市进萄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锐峭,老刑警劉巖中鼠,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異沿癞,居然都是意外死亡援雇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門抛寝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)熊杨,“玉大人,你說(shuō)我怎么就攤上這事盗舰【Ц” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵钻趋,是天一觀的道長(zhǎng)川陆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蛮位,這世上最難降的妖魔是什么较沪? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮失仁,結(jié)果婚禮上尸曼,老公的妹妹穿的比我還像新娘。我一直安慰自己萄焦,他們只是感情好控轿,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布冤竹。 她就那樣靜靜地躺著,像睡著了一般茬射。 火紅的嫁衣襯著肌膚如雪鹦蠕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天在抛,我揣著相機(jī)與錄音钟病,去河邊找鬼。 笑死刚梭,一個(gè)胖子當(dāng)著我的面吹牛肠阱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播望浩,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼辖所,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了磨德?” 一聲冷哼從身側(cè)響起缘回,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎典挑,沒(méi)想到半個(gè)月后酥宴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡您觉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年拙寡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琳水。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肆糕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出在孝,到底是詐尸還是另有隱情诚啃,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布私沮,位于F島的核電站始赎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏仔燕。R本人自食惡果不足惜造垛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晰搀。 院中可真熱鬧五辽,春花似錦、人聲如沸外恕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至髓迎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間建丧,已是汗流浹背排龄。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翎朱,地道東北人橄维。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像拴曲,于是被迫代替她去往敵國(guó)和親争舞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344