scala-隱式機制及Akka

隱式機制及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的信息后,根據需要作出各種響應,消息的類型可以是任意的,消息的內容也可以是任意的。


Akka.png

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)
    }
  }
}
第九個模塊錯題集.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容