隱式機制及Akka
隱式轉換
隱式轉換和隱式參數時Scala中兩個非常強大的功能,利用隱式轉換和隱式參數,可以提供類庫,對類庫的使用者隱匿掉具體細節(jié)。
Scala會根據隱式轉化函數的簽名,在程序中使用隱式轉換函數接收的參數類型定義的對象時,會自動將其傳入隱式轉換函數,轉換為另一種類型的對象并返回,這就是隱式轉換
- 首先有一個隱式轉換函數
- 使用到隱式轉換函數接收的參數類型定義的對象
- Scala自動傳入隱式轉換函數,并完成對象的類型轉換
隱式轉換需要使用implicit關鍵字。
使用Scala隱式轉化有一定的限制:
- implicit關鍵字只能用來修飾方法、變量、參數
- 隱式轉換的函數只在當前范圍內才有效。如果隱式轉換不在當前范圍內定義,那么必須通過import語句將其導入
Spark源碼中有大量的隱式轉換和隱式參數,因此必須掌握
隱式轉換函數
Scala的隱式轉換最核心的就是定義隱式轉換函數,即 implicit conversion function。
定義隱式轉換函數,只要在編寫程序內引入,就會被Scala自動使用。
隱式轉換函數有Scala自動調用,通常建議將隱式函數命名問one2one的形式
示例一:
package hhb.cn.part10
class Num{}
class RichNum(num: Num) {
def rich(): Unit = {
println("============")
}
}
/**
* 自定義了一個伴生對象,生成一個apply方法
*/
object RichNum {
def apply(num: Num): RichNum = {
new RichNum(num)
}
}
object ImplicitDemo {
//定義一個隱式轉換函數,命名要符合one2one的命名格式
implicit def num2RichNum(num: Num): RichNum = {
RichNum(num)
}
def main(args: Array[String]): Unit = {
val num = new Num
//num類型并沒有rich方法,但是Scala編譯器會查找當前范圍內的隱式轉換函數,
//然后將其轉換成RichNum類型,最終調用rich方法
num.rich()
}
}
示例二:導入隱式函數
package hhb.cn.part10
object IntToStringImplicit {
implicit def int2String(num: Int): String = {
num.toString
}
}
下面代碼中調用了String類型的length方法,Int類型本身沒有l(wèi)ength方法,但是在可用范圍內定義了可以把Int轉換為String的隱式函數int2String,因此函數編譯通過并運行出正確的結果。
此示例中隱式函數的定義必須定義在使用之前,否則編譯報錯。
package hhb.cn.part10
import hhb.cn.part10.IntToStringImplicit.int2String
object ImplicitDemoTwo {
def main(args: Array[String]): Unit = {
val num = 10
println(num.length)
}
}
通過hhb.cn.part10.IntToStringImplicit.int2String,將Int2StringTest內部的成員導入到相應的作用域內,否則無法調用隱式函數。
要實現隱式轉換,只要在程序可見的范圍內定義隱式轉換函數即可,Scala會自動使用隱式轉換函數。隱式轉換函數與普通函數的語法區(qū)別就是,要以implicit開頭,而且最好要定義函數返回類型。
隱式轉換案例:特殊售票窗口(只接受特殊人群買票,比如學生、老人等),其他人不能在特殊售票窗口買票。
package hhb.cn.part10
class SpecialPerson(var name: String)
class Older(var name: String)
class Worker(var name: String)
class Student(var name: String)
object ImplicitDemoThree {
def SpecialBuyTick(specialPerson: SpecialPerson) = {
if (specialPerson != null)
println(specialPerson.name + " 購買了特殊票")
else
println("不能買")
}
//定義一個隱式轉換函數
implicit def anyToSpecial(any: Any): SpecialPerson = {
any match {
case any: Older => new SpecialPerson(any.asInstanceOf[Older].name)
case any: Student => new SpecialPerson(any.asInstanceOf[Student].name)
case _ => null
}
}
def main(args: Array[String]): Unit = {
val older = new Older("older")
val student = new Student("student")
val worker = new Worker("worker")
SpecialBuyTick(older)
SpecialBuyTick(student)
SpecialBuyTick(worker)
}
}
隱式參數和隱式值
在函數定義的時候,支持在最后一組參數中使用implicit,表明這是一組隱式參數,在調用該函數的時候,可以不用傳遞隱式參數,而編譯器會自動尋找一個 implicit 標記過的合適的值作為參數
Scala編譯器會在兩個范圍內查找:
- 當前作用域內可見的val或者var定義隱式變量
- 隱式參數類型的伴生對象內隱式值
object Doubly {
//在print函數中定義一個隱式參數fmt
def print(num: Double)(implicit fmt: String): Unit = {
println(fmt format (num))
}
def main(args: Array[String]): Unit = {
//此時調用print函數需要為第二個隱式參數賦值
print(3.12)("%.1f")
//定義一個隱式變量
implicit val printFmt="%.3f"
//當調用print函數時沒有給第二個隱式參數賦值,
//那么Scala會在當前作用域內尋找可見的val或var定義的隱式變量,一旦找到就會應用
print(3.12)
}
}
類型參數
Scala類型參數與Java的泛型是一樣的,可以在集合、類、函數中定義類型參數,從而保證程序更好的健壯性。
泛型類
泛型類,顧名思義,其實就是在類的聲明中定義一些泛型類型,然后在類內部的字段或方法。就可以使用這些泛型類型。
使用泛型類,通常是需要對類中的某些成員變量,比如某個字段和方法中的參數或變量進行統(tǒng)一的類型限制,這樣可以保證程序更好的健壯性和穩(wěn)定性。
如果不使用泛型進行統(tǒng)一的類型限制,那么在后期程序運行過程中難免會出現問題,比如傳入了不希望的類型導致程序出問題。
在使用泛型類的時候,比如創(chuàng)建泛型類的對象,只需將類型參數替換成時間的畸形即可。
Scala自動推斷泛型類型特效,直接給使用泛型的字段賦值時,Scala會自動進行類型推斷
泛型類的定義如下:
//定義一個泛型類
class Spark[T1, T2, T3](n: T1) {
var name: T1 = n
var age: T2 = _
var address: T3 = _
def getInfo(): Unit = {
println(s"$name,$age,$address")
}
}
使用上述的泛型類,只需要使用具體的類型代替類型參數即可。
object GenericDemo extends App {
val spark = new Spark[String, Int, String]("zhangsan")
spark.getInfo() //zhangsan,null,null
spark.age = 20
spark.address = "123"
spark.getInfo() //zhangsan,20,123
}
泛型函數
泛型函數,與泛型類類似,可以給某個函數在聲明時指定泛型類型,然后在函數體內,多個變量或者返回值之間,就可以使用泛型類型進行聲明,從而對某個特殊的變量,或者多個變量,進行強制性的類型限制。
與泛型類一樣,你可以通過給使用了泛型類型的變量傳遞值來讓Scala自動推斷泛型的實際類型,也可以在調用函數時,手動指定泛型類型。
案例:卡片售賣機,可以指定卡片的內容,內容可以是String類型或Int類型
object GenericFunc {
def getCart[T](context: T): Unit = {
context match {
case context: Int => s"cart : $context is Int"
case context: String => s"cart : $context is String"
case _ => "other"
}
}
def main(args: Array[String]): Unit = {
println(getCart[String]("12312313"))
println(getCart(123))
println(getCart(123.0))
}
}
協變和逆變
Scala的協變和逆變是非常有特色的,完全解決的Java中泛型的一大缺憾
舉例來說,Java中,如果Process是Master的子類,那么Cart[Process]卻不是Card[Master]的子類。而Scala中,只要靈活的使用協變和逆變就可以解決Java泛型的問題。
協變定義形式如:trait List[+T]{}
當類型s是類型A的子類型時,則List[s]也可以認為是List[A]的子類,即List[S]可以泛化List[A],也就是被參數化,類型的泛化方向與參數類型一致,所以被稱為協變(covariance)。
逆變定義形式如:trait List[-T] {}
與協變定義相反,類型的泛化方向與參數方向相反,被稱為逆變(contravariance),當類型S是類型A的子類型時,則List[A]也可以認為是List[S]的子類
小結:如果A是B的子類,那么在協變中,List[A]就是List[B]的子類,在逆變中,List[A]就是List[B]的父類
package hhb.cn.part11
//大師
class Master
//專家
class Process extends Master
//講師
class Teacher
//這是一個協變,Process是Master的子類,那么Card[Process] 也是 Card[Master] 的子類
class Card[+T]
class Card2[-T]
object CovarianceDemo {
//表示只有Card[Master]以及Card[Master]的子類Card[Process]才能進入
def enterMeet(card: Card[Master]): Unit = {
println("進入")
}
//表示只有Card[Master]以及Card[Master]的子類Card[Process]才能進入
def enterMeet2(card: Card2[Process]): Unit = {
println("進入")
}
def main(args: Array[String]): Unit = {
val master = new Card[Master]
val process = new Card[Process]
val teacher = new Card[Teacher]
enterMeet(master)
enterMeet(process)
//直接報錯
//enterMeet(teacher)
val master2 = new Card2[Master]
val process2 = new Card2[Process]
val teacher2 = new Card2[Teacher]
enterMeet2(master2)
enterMeet2(process2)
//直接報錯
//enterMeet(teacher2)
}
}
Akka
Akka是Java虛擬機平臺上構建高并發(fā)、分布式和容錯應用的工具包和運行時。是使用Scala語言編寫,同時提供了Scala和Java開發(fā)的接口。Akka處理并發(fā)的方法基于Actor模型,Actor之間通信的唯一機制就是消息傳遞。
Actor
Scala的Actor類似與Java 中的多線程,但是不同的是,Scala的Actor提供的模型與多線程不同,Actor盡可能的避免鎖和共享狀態(tài),從而避免多線程并發(fā)時出現資源爭用的情況,進而提升多線程編程的性能。
Actor可以看作一個個獨立的實體,Actor之間可以通過交換消息的方式進行通信,每個Actor都有自己的收件箱(MailBox)。一個Actor收到其他Actor的信息后,根據需要作出各種響應,消息的類型可以是任意的,消息的內容也可以是任意的。

ActorSystem
在Akka中,ActorSystem是一個重量級結構。他需要分配多個線程,所以在實際應用中,ActorSystem通常是一個單例對象,我們可以使用ActorSystem創(chuàng)建很多Actor。
Akka案例:
創(chuàng)建一個maven項目,在項目的pom文件中增加如下依賴:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>ScalaMavenPro</groupId>
<artifactId>ScalaMavenPro</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 定義一下常量-->
<properties>
<encoding>UTF-8</encoding>
<scala.version>2.13.3</scala.version>
</properties>
<dependencies>
<!-- 添加akka的actor依賴 -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-actors</artifactId>
<version>2.11.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.typesafe.akka/akka-actor -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.13</artifactId>
<version>2.6.8</version>
</dependency>
<!-- 多進程之間的Actor通信 -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-remote_2.13</artifactId>
<version>2.6.8</version>
</dependency>
</dependencies>
</project>
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import scala.io.StdIn
class Message extends Actor {
override def receive: Receive = {
case "您好" => println("你好呀!")
case "干什么呢" => println("沒干啥")
case "拜拜" => {
//關閉自己
context.stop(self)
//關閉SystemActor
context.system.terminate()
}
}
}
object ActorDemo {
def main(args: Array[String]): Unit = {
val myActorSystem = ActorSystem("myActorSystem")
val messageActorRef: ActorRef = myActorSystem.actorOf(Props[Message], "message")
var flag = true
while (flag) {
print("請輸入想發(fā)送的消息:")
val str = StdIn.readLine()
messageActorRef ! str
if (str.equals("拜拜")) {
flag = false
}
Thread.sleep(100)
}
}
}
