Scala筆記

 

目標1:熟練使用scala編寫Spark程序

 

 

目標2:動手編寫一個簡易版的Spark通訊框架

 

目標3:爲閱讀Spark內核源碼作準備

 

 

 

 

二、  scala的基本介紹

一、什麼是Scala

         scala官方網址:html

         http://www.scala-lang.org前端

Scala是一種多範式的編程語言,其設計的初衷是要集成面向對象編程和函數式編程的各類特性。Scala運行於Java平臺(Java虛擬機),併兼容現有的Java程序。http://www.scala-lang.orgjava

 

二、 爲何要學Scala

一、優雅:這是框架設計師第一個要考慮的問題,框架的用戶是應用開發程序員,API是否優雅直接影響用戶體驗。react

二、速度快:Scala語言表達能力強,一行代碼抵得上Java多行,開發速度快;Scala是靜態編譯的,因此和JRuby,Groovy比起來速度會快不少。程序員

三、能融合到Hadoop生態圈:Hadoop如今是大數據事實標準,Spark並非要取代Hadoop,而是要完善Hadoop生態。JVM語言大部分可能會想到Java,但Java作出來的API太醜,或者想實現一個優雅的API太費勁。 es6

 

三、  Scala編譯器安裝

一、 安裝JDK

由於Scala是運行在JVM平臺上的,因此安裝Scala以前要安裝JDK。正則表達式

二、      \d{4}(\-|\/|.)\d{1,2}\1\d{1,2}\s\d\d\D\d\d\D\d\d\D\d{1,10}\s\w{1,10}\s{2}\w{1,20}\D\w{1,20}\s\D\w{1,50}\D\w{4}\D\w{6}\D\d{3}\D\D\s\D\s\w{1,11}\s\w{1,12}\d{1,15}\w\d{1,10}\s\w{12}\s\w{4}\s\w{1,15}\s\w\w\s\w{1,20}安裝Scala

Windows安裝Scala編譯器

訪問Scala官網http://www.scala-lang.org/下載Scala編譯器安裝包,目前最新版本是2.12.x,這裏下載scala-2.11.8.msi後點擊下一步就能夠了(自動配置上環境變量)。也能夠下載scala-2.11.8.zip,解壓後配置上環境變量就能夠了。算法

Linux安裝Scala編譯器

下載Scala地址https://www.scala-lang.org/download/2.11.8.htmlapache

而後解壓Scala到指定目錄編程

tar -zxvf scala-2.11.8.tgz -C /usr/java

配置環境變量,將scala加入到PATH中

vi /etc/profile

export JAVA_HOME=/usr/java/jdk1.8

export PATH=$PATH:$JAVA_HOME/bin:/usr/java/scala-2.11.8/bin

 

 Scala開發工具安裝

目前Scala的開發工具主要有兩種:Eclipse和IDEA,這兩個開發工具都有相應的Scala插件,若是使用Eclipse,直接到Scala官網下載便可http://scala-ide.org/download/sdk.html。

因爲IDEA的Scala插件更優秀,大多數Scala程序員都選擇IDEA,能夠到http://www.jetbrains.com/idea/download/下載,點擊下一步安裝便可,安裝時若是有網絡能夠選擇在線安裝Scala插件。

這裏咱們使用離線安裝Scala插件:

1.安裝IDEA,點擊下一步便可。

2.下載IEDA的scala插件

插件地址: https://plugins.jetbrains.com/plugin/1347-scala

3.安裝Scala插件:Configure -> Plugins -> Install plugin from disk -> 選擇Scala插件 -> OK -> 重啓IDEA

 

 

 

 

 

 

 

 

 

 

 

 

 

三、scala的REPL

REPL ==> 交互式解析器環境

R(read)、E(evaluate) 、P(print)、L(loop)

輸入值,交互式解析器會讀取輸入內容並對它求值,再返回結果,並重復此過程。(所見即所得)

REPL特性:

變量在會話週期內一直可用

多行代碼和單行代碼一塊兒編譯

支持連接外部庫和代碼

REPL歷史命令跨會話存儲

在命令行輸入scala以啓動scala  REPL

 

 

 

四、      建立工程支持scala代碼開發

第一步:idea當中建立建立普通maven工程

File   ==>  New  ==>  Project

 

 

 

第二步:修改pom.xml添加scala的版本以及打包插件

<dependencies>

        <dependency>

            <groupId>org.scala-lang</groupId>

            <artifactId>scala-library</artifactId>

            <version>2.11.8</version>

            <!-- 若是想要用java -jar 來運行咱們打包以後的jar包,則下面這個配置必須註釋掉 -->

           <!-- <scope>provided</scope>-->

        </dependency>

    </dependencies>

 

    <build>

        <plugins>

            <!-- 限制jdk版本插件 -->

            <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-compiler-plugin</artifactId>

                <version>3.0</version>

                <configuration>

                    <source>1.8</source>

                    <target>1.8</target>

                    <encoding>UTF-8</encoding>

                </configuration>

            </plugin>

        <!-- 編譯scala須要用到的插件 -->

            <plugin>

                <groupId>net.alchim31.maven</groupId>

                <artifactId>scala-maven-plugin</artifactId>

                <version>3.2.2</version>

                <executions>

                    <execution>

                        <goals>

                            <goal>compile</goal>

                            <goal>testCompile</goal>

                        </goals>

                    </execution>

                </executions>

            </plugin>

            <!-- 項目打包用到的插件 -->

            <plugin>

                <artifactId>maven-assembly-plugin</artifactId>

                <configuration>

                    <descriptorRefs>

                        <descriptorRef>jar-with-dependencies</descriptorRef>

                    </descriptorRefs>

                    <archive>

                        <manifest>

                            <mainClass>cn.itcast.scala.demo1.ScalaFirst</mainClass>

                        </manifest>

                    </archive>

                </configuration>

                <executions>

                    <execution>

                        <id>make-assembly</id>

                        <phase>package</phase>

                        <goals>

                            <goal>single</goal>

                        </goals>

                    </execution>

                </executions>

            </plugin>

        </plugins>

    </build>

第三步:建立scala代碼保存的文件夾

src  ==> main  ==>  new  ==> Directory ==>  scala

 

 

 

第四步:開發scala的代碼並打包運行

 

 

開發代碼以下:

 

object ScalaFirst {
  def main(args: Array[String]): Unit = {
    println("hello world")
  }

}

 

第五步:打包咱們的scala代碼並準備運行

將咱們的代碼打包,以後,進行運行

雙擊package以後,就會出現咱們打好的jar包,而後選擇下面這個就能夠運行了

 

運行咱們的代碼一共能夠有四個命令,兩種是打包的時候選擇了咱們的main程序類的,兩種是咱們打包時候沒有選擇main程序類的

其中第一種和第三種,都是選定了咱們的main程序類

第二種和第四種都是沒有選定咱們的main程序類

四種運行方式均可以用於運行咱們的jar包

 

第一種運行方式:咱們打包的時候指定了對應的main方法所在的程序類

scala scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar

第二種運行方式:無論打包的時候有沒有指定main方法所在的程序類,均可以運行

scala -cp scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar cn.itcast.scala.demo1.ScalaFirst

第三種方式:咱們打包的時候指定了對應的main方法所在的程序類

java -jar scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar

第四種方式:無論打包的時候有沒有指定main方法所在的程序類,均可以運行

java -cp scaladay01-1.0-SNAPSHOT-jar-with-dependencies.jar cn.itcast.scala.demo1.ScalaFirst

 

 

咱們能夠看到咱們的scala代碼最終也編譯成了class文件

 

 

四、scala基礎入門

一、scala當中申明值和變量

scala當中的變量申明可使用兩種方式,第一種使用val來申明變量。第二種使用var來申明變量。

申明變量語法

val/var  變量名  [:變量類型]  =  變量值

其中val定義的變量是不可變的。相似於java當中使用final來進行修飾

 

 

 

 

 

 

 

 

REPL自動分配變量名

若是咱們在REPL當中沒有定義變量名,那麼咱們的變量名系統會自動給定

 

 

注意:scala當中的變量類型能夠不用指定,系統會自動推斷。爲了減小可變性引發的bug,scala當中推薦儘可能使用不可變類型來申明變量。var和val申明變量的時候,變量都必須初始化

 

二、塊表達式

定義變量時用 {} 包含一系列表達式,其中塊的最後一個表達式的值就是塊的值。

var hello = {
  println("world")
  val d = 20
  val c = 10
  d-c
}

塊表達式最後的結果,就是咱們變量的值

 

三、scala當中經常使用數據類型

Scala和Java同樣,有7種數值類型Byte、Char、Short、Int、Long、Float、Double類型和1個Boolean類型。

Boolean

true 或者 false

Byte

8位, 有符號

Short

16位, 有符號

Int

32位, 有符號

Long

64位, 有符號

Char

16位, 無符號

Float

32位, 單精度浮點數

Double

64位, 雙精度浮點數

String

其實就是由Char數組組成

 

與java當中不一樣,scala當中並不區分基本數據類型和引用數據類型,全部的這些類型所有都是對象,能夠調用相對應的方法。在scala當中,String直接引用的是java.lang.String這個java當中的類型。因爲String在須要時能隱式轉換爲StringOps,所以不須要任何額外的轉換,String就可使用這些方法。

每一種數據類型都有對應的Rich* 類型,如RichInt、RichChar等,爲基本類型提供了更多的有用操做。

 

四、scala當中的經常使用類型結構圖

Scala中,全部的值都是類對象,而全部的類,包括值類型,都最終繼承自一個統一的根類型Any。統一類型,是Scala的又一大特色。更特別的是,Scala中還定義了幾個底層類(Bottom Class),好比Null和Nothing。

1)   Null是全部引用類型的子類型,而Nothing是全部類型的子類型。Null類只有一個實例對象,null,相似於Java中的null引用。null能夠賦值給任意引用類型,可是不能賦值給值類型。

2)   Nothing,能夠做爲沒有正常返回值的方法的返回類型,很是直觀的告訴你這個方法不會正常返回,並且因爲Nothing是其餘任意類型的子類,他還能跟要求返回值的方法兼容。

3)   Unit類型用來標識過程,也就是沒有明確返回值的函數。 因而可知,Unit相似於Java裏的void。Unit只有一個實例,(),這個實例也沒有實質的意義。

 

 

 

 

五、算數操做符重載

+-*/%能夠完成和Java中相同的工做,可是有一點區別,他們都是方法。你幾乎能夠用任何符號來爲方法命名。

舉例:

scala> 1 + 2

等同於:

scala> 1.+(2)

 

注意:Scala中沒有++、--操做符,須要經過+=、-=來實現一樣的效果。

 

 

 

 

 

五、流程控制語句以及方法和函數

一、if  else表達式

scala中沒有三目運算符,由於根本不須要。scala中if else表達式是有返回值的,若是if或者else返回的類型不同,就返回Any類型(全部類型的公共超類型)。

例如:if else返回類型同樣

val a = 20
val b = if(a >10){
  15
}else{
  35
}

 

例如:if else返回類型不同

val c = 50
val d = if(c > 20){
  println("返回一個字符串")
  "ABC"
}else{
  println("helloworld")
}

 

 

若是缺乏一個判斷,什麼都沒有返回,可是Scala認爲任何表達式都會有值,對於空值,使用Unit類,寫作(),叫作無用佔位符,至關於java中的void。

注意:行尾的位置不須要分號,只要可以從上下文判斷出語句的終止便可。可是若是在單行中寫多個語句,則須要分號分割。在Scala中,{}快包含一系列表達式,其結果也是一個表達式。塊中最後一個表達式的值就是塊的值。

 

二、while表達式

scala提供了相似於java的while和do循環,可是while語句的自己是沒有任何返回值類型的,也就是while語句最終的返回結果是Unit類型的()。

var e = 1;
val f = while(e <= 10){
  e +=1
}
println(e)
println(f)

 

scala當中while循環的contine和break:注意:scala當中並無提供相似於java的continue和break操做,若是須要終止循環,咱們能夠有如下幾種方式

一、  使用Boolean標識來進行終端

二、  使用嵌套函數,從函數中直接return

三、  使用Breaks對象當中的break方法

var g = 10
val loop = new Breaks
loop.breakable{
val h =   while(g <=20){
    g +=1
    if(g == 15){
      loop.break()
    }
  }
  println(h)
}

println(g+"=============")

 

三、for表達式

scala當中,爲for循環這一經常使用的循環結構提供了不少的特性,這些特性被稱之爲for推導式或者for表達式

示例一:使用to實現左右兩邊均爲閉合的訪問

for(i <- 1 to 3; j <- 1 to 5){
  println( i *j +" result result result result")

}

 

示例二:使用util實現左右兩邊分別爲前閉後開的訪問

for(i <- 1 until 5 ;j <- 2 until  5){
  println(i * j )
}

 

示例三:引入保護式(也稱條件判斷式)。咱們能夠在for循環條件裏面加入判斷表達式,若是知足則進入for循環,若是不知足則不進入for循環。相似於java當中的continue的功能相似

for(i <- 1 to 5 if i!=2){
  println(i)
}

 

 

示例四:引入變量

for(i <- 1 to 3 ;j = 4-i){
  println(i * j)
}

 

示例五:將遍歷過程當中處理的結果返回到一個變量,使用yield關鍵字進行接收

val for5 = for(i <- 1 to 10) yield  i
println(for5+"for5")

示例六:使用大括號代替小括號

for{
  i <- 1 to 5
  j = 5-i
}
  println( i* j +"myij")

 

 

 

四、調用函數與方法

在scala中,通常狀況下咱們不會刻意的去區分函數與方法的區別,可是他們確實是不一樣的東西。後面咱們再詳細探討。首先咱們要學會使用scala來調用函數與方法。

1) 調用函數,求方根

scala> import scala.math._

scala> sqrt(100)

2) 調用方法,靜態方法(scala中沒有靜態方法這個概念,須要經過伴生類對象來實現)

生成一個隨機的素數

scala> BigInt.probablePrime(16, scala.util.Random)

3) 調用方法,非靜態方法,使用對象調用

scala> "HelloWorld".distinct

4) apply與update方法

apply方法是調用時能夠省略方法名的方法。用於構造和獲取元素:

"Hello"(4)  等同於  "Hello".apply(4)

Array(1,2,3) 等同於 Array.apply(1,2,3)

如:

println("Hello"(4))

println("Hello".apply(4))

在StringOps中你會發現一個 def apply(n: Int): Char方法定義。update方法也是調用時能夠省略方法名的方法,用於元素的更新:

arr(4) = 5  等同於  arr.update(4,5)

如:

val arr1 = new Array[Int](5)

arr1(1) = 2

arr1.update(1, 2)

println(arr1.mkString(","))

 

 

五、scala當中的函數與方法

在scala當中,函數與方法是兩個不一樣的概念,函數是scala當中的一等公民,scala是一門函數式的編程語言,同時兼顧了面嚮對象語言的特性

 

scala當中方法的定義

 

scala定義方法的標準格式爲

def 方法名(參數名1: 參數類型1, 參數名2: 參數類型2) : 返回類型 = {方法體}

 

示例一:定義一個最標準的方法,且定義方法的返回值類型爲Int類型

def hello(first:String,second:Int) :Int = {
  second
}

 

 

示例二:定義一個方法,且不定義返回值

注意:若是定義的方法沒有返回值,那麼方法的返回值會作自動推斷。根據咱們方法的最後一個返回類型來推斷咱們的方法返回類型

 

def  hello2(first:Int , second:String) ={
  //println(first)
  //20
}
val hello2Result = hello2(20,"abc")
println( hello2Result)

 

示例三:定義一個方法,不定義返回值,能夠經過自動推斷,返回不一樣類型的值

 

def hello3(first:Int,second:String) ={
  if(first > 10){
    first
  }else{
    second
  }
}
val hello3Result = hello3(5,"helloworld")
println(hello3Result)

 

示例四:定義一個方法,參數給定默認值,若是不傳入參數,就使用默認值來代替

def hello4(first:Int = 10,second:String)={
  println(first+"\t"+ second)
}
//注意咱們在調用方法的時候咱們能夠經過參數名來指定咱們的參數的值
hello4(second="helloworld")

 

 

示例五:變長參數,方法的參數個數不定的,相似於java當中的方法的...可變參數

def hello5(first:Int*)={
  var result = 0;
  for(arg <- first){
    result  += arg
  }
  println(result)
}
hello5(10,20,30)
hello5(10,50)

 

示例六:遞歸函數。咱們能夠定義一個方法,使得方法本身調用本身,造成一個遞歸函數,可是方法的返回值類型必須顯示的手動指定

def  hello6(first:Int):Int={
  if(first <= 1){
    1
  }else{
    first * hello6(first -1)
  }
}

val hello6Result = hello6(10)
println(hello6Result)

 

示例七:定義一個方法,沒有顯示的指定返回值,那麼咱們方法當中定義的等號能夠省掉

注意:若是省掉了=號,那麼這個方法強調的就是一個代碼執行的過程

/**
  * 定義了一個方法,可是方法的返回值沒有顯示指定,
  * 此時咱們就能夠省掉方法定義的=號,若是省掉 = 號,
  * 那麼這個方法強調的是一個過程,代碼執行的過程,
  * 不會產生任何的返回值
  * @param first
 
*/
def hello7(first:Int){
  println(first)
  30
}
hello7(20)

 

示例八:直接經過def定義一個方法

def hello8=10;
val hello8Result = hello8
println(hello8Result)

 

 

示例九:若是方法體當中只有一行代碼,咱們也能夠省掉大括號

def hello10(first:Int,second:Int) = first+second
val hello10Result = hello10(10,20)
println(hello10Result)

 

 

scala當中函數的定義

函數定義的兩種形式

第一種形式:

val  函數名 = (參數名1:參數類型1,參數名2:參數類型2)  =>  {函數體}

 

第二種形式:

val  函數名 :(參數類型1,參數類型2) => (返回類型) = {

         函數體

}

 

 

示例一:定義一個標準函數,使用 =>來進行定義

val func1 =(x:Int,y:Int) =>{
  x+y
}
func1(2,8)

 

示例二:定義匿名函數。也就是咱們能夠定義一個沒有名字的函數

定義一個匿名函數以後,這個函數就無法使用了

 

(x:Int,y:String) =>{x + y}

 

示例三:函數定義的另一種形式,定義一個函數,參數只有一個且是Int類型,返回值也是Int類型

 

 

val func3 :Int => Int = {x => x * x }
val func3Result = func3(10)

 

示例四:定義一個函數,參數值是兩個,分別是Int和String,返回值是一個元組,分別是String和Int

val func4:(Int,String) =>(String,Int) ={
  (x,y) => (y,x)
}
val func4Result = func4(10,"hello")
println(func4Result)

 

 

 

scala當中函數與方法的區別以及方法轉換成函數

在scala當中,函數與方法是有區別的,函數能夠做爲一個參數,傳入到方法裏面去

咱們能夠定義一個函數,而後再定義一個方法,可是方法的參數是一個函數

val myFunc = (x:Int) =>{
  x * x
}
val myFunc2 :(Int) => Int ={
  x => x * x
}

def methodFunction(f:Int => Int):Int ={
    println(f(100))
  f(100)
}
val methodFunctionResult = methodFunction(myFunc)
val methodFunctionResult2 = methodFunction(myFunc2)
println(methodFunctionResult)
println(methodFunctionResult2)

 

 

方法能夠自動轉換成函數做爲參數傳遞到方法裏面去

def method2(x:Int) ={ x * x }
def methodFunc2(x:Int => Int):Int ={
  x(100)
}
val methodFunc2Result = methodFunc2(method2)
println(methodFunc2Result)

 

咱們能夠經過 _  將咱們的一個方法,轉換成函數

 

def method3(x:Int,y:String ) :Int = {
  println(x)
  x
}
val methodToFunc = method3 _
println( methodToFunc)

 

 

六、懶值加載

當val被聲明爲lazy時,他的初始化將被推遲,直到咱們首次對此取值,適用於初始化開銷較大的場景。

def init(): String = {
  println("init方法執行")
  "嘿嘿嘿,喵喵喵~"
}
lazy val msg = init()
println("lazy方法沒有執行")
println(msg)

 

 

六、數據結構

6.一、數據結構特色

Scala同時支持可變集合和不可變集合,不可變集合從不可變,能夠安全的併發訪問。

兩個主要的包:

不可變集合:scala.collection.immutable

可變集合:  scala.collection.mutable

Scala優先採用不可變集合,對於幾乎全部的集合類,Scala都同時提供了可變和不可變的版本。

不可變集合繼承層次:

 

可變集合繼承層次:

 

 

6.二、數組

一、定義定長數組

咱們能夠定義一個固定長度大小和類型的定長數組

//定義一個數組長度爲10,類型爲Int的固定大小數組
val array = new Array[Int](10)
array(1) = 10
array(2) = 20
//訪問數組當中的某一個下標元素值
println
(array(1))
//直接使用apply方法進行生成一個數組
val array2 = Array(1,2,3)
//訪問數組的元素
println
(array2(2))

 

二、變長數組

咱們也能夠經過ArrayBuffer來定義一個變長數組

val array3 = new ArrayBuffer[Int]()
array3.append(20)
val array4 =  ArrayBuffer[String]()
array4.append("helloworld")

 

三、定長數組與變長數組的相互轉換

定長數組轉換成變長數組

 

//定長數組轉換成變長數組

val toBuffer = array.toBuffer

toBuffer.append(50)

 

變長數組轉換爲定長數組

//變長數組準換成定長數組

val toArray = array3.toArray

 

 

 

四、多維數組

咱們能夠經過Array的ofDim方法來定義一個多維的數組,多少行,多少列,都是咱們本身定義說了算

val dim = Array.ofDim[Double](3,4)
dim(1)(1) = 11.11
println(dim.mkString(","))

 

五、scala當中數組的遍歷

val array5 = ArrayBuffer(1,2,3,4,5,6)
for(x <- array5){
  println(x )
}

 

 

五、       數組的常見算法

val array6 = Array(1,2,3,4,5,6)
//求和
array6.sum
//求最大值
array6.max
//排序
array6.sorted

 

6.三、元組tuple

在scala當中提供元組tuple的數據類型,能夠理解tuple爲一個容器,能夠存放各類不一樣的數據類型的數據,例如一個Tuple當中既能夠存放String類型數據,同時也能夠存放Int類型的數據

注意:注意元組一旦建立以後,就是不可變的,也就是說元組當中沒有添加和刪除元素這一說

 

一、 建立元組

建立元組,直接使用小括號,小括號當中存放咱們元組當中各類類型的元素便可

val tuple1 = ("hello",1,5.0f)
println(tuple1)

 

二、元組數據的訪問

訪問元組當中的數據直接使用_加角標便可,可是要注意,元組當中的數據角標是從1開始的

val tuple1 = ("hello",1,5.0f)
println(tuple1)

val tuple1Result = tuple1._1
println(tuple1Result)

 

 

三、元組的遍歷

 

val tuple1 = ("hello",1,5.0f)

println(tuple1)

 

val tuple1Result = tuple1._1

println(tuple1Result)

 

//第一種方式遍歷元組

for(x <-  tuple1.productIterator){

  println(x)

}

//第二種方式遍歷元組

tuple1.productIterator.foreach( x => println(x))

 

 

6.四、映射Map

scala當中的Map集合與java當中的Map相似,也是key,value對形式的

 

一、不可變映射

val map1 = Map("hello" ->"world","name" -> "zhangsan","age" -> 18)

 

二、 可變映射及其操做

val map2 = scala.collection.mutable.Map("hello" ->"world","name" -> "zhangsan","age" -> 18)
//可變map添加元素
map2.+=("address" ->"地球")
println(map2)
//可變map刪除元素.注意,刪除元素是返回一個刪除元素以後的map,原來的map並無改變
val map3 = map2.-("address")
println(map2)
println(map3)

//或者使用覆蓋key的方式來更細元素
map2 += ("address" -> "北京")
println(map2)
//或者使用 + 來進行更新元素
//
注意,map當中沒有phonNo這個key,則不能更細
map2 +("address" ->"上海","phonNo" -> "13688886666")
println(map2)

 

三、獲取map當中指定的key值

//經過key來進行取值
map2.get("address")
//經過key來進行取值,若是沒有這個key,就用後面給定的默認值
map2.getOrElse("address","非洲")
//經過key來進行取值,真的沒有這個key,那麼就用後面給定的默認值
map2.getOrElse("phoNo","13133335555")

 

 

四、遍歷Map當中的元素


//遍歷key與value
for((k,v) <- map2){
  println(k)
  println(v)
}

//遍歷獲取全部的key
for(k <- map2.keys) {
  println(k)
}
//遍歷獲取全部的value
for(v <- map2.values) {
  println(v)
}

//打印key,value
for(kv <- map2){
  println(kv)
}

 

五、將對偶的數組轉變爲map

//將對偶的元組轉變爲map
val  arrayMap = Array(("name","zhangsan"),("age",28))
val toMap = arrayMap.toMap
println(toMap)

 

6.五、列表(List)

scala當中也提供有與java相似的List集合操做

一、 建立列表

注意:列表當中的元素類型能夠是不一樣的,這一點與咱們元組相似,可是列表當中的元素是能夠刪減的

val list1 = List("hello",20,5.0f)
println(list1)

 

二、訪問列表當中的元素

//訪問列表當中的元素
val list1Result = list1(0)
println(list1Result)

 

三、列表當中添加元素

咱們能夠從列表頭部或者尾部添加元素

val list2  = list1:+50
val list3 = 100+:list1
println(list2)
println(list3)

 

四、List的建立與追加元素

Nil是一個空的List,定義爲List[Nothing]

//尾部添加了Nil,那麼就會出現List集合裏面裝List集合的現象
val list4 = 1::2 ::3 :: list1 ::Nil
println(list4)
//尾部沒有添加Nil的值,那麼全部的元素都壓平到一個集合裏面去了
val list5 = 1::2::3::list1
println(list5)

 

五、變長List的建立與使用

val  list6 = new ListBuffer[String]
list6.append("hello")
list6.append("world")
println(list6.mkString(","))
val list7 = list6.toList
println(list7)

 

六、       List操做延伸閱讀

 

Scala是函數式風格與面向對象共存的編程語言,方法不該該有反作用是函數風格編程的一個重要的理念。方法惟一的效果應該是計算並返回值,用這種方式工做的好處就是方法之間不多糾纏在一塊兒,所以就更加可靠和可重用。另外一個好處(靜態類型語言)是傳入傳出方法的全部東西都被類型檢查器檢查,所以邏輯錯誤會更有可能把本身表現爲類型錯誤。把這個函數式編程的哲學應用到對象世界裏覺得着使對象不可變。
前面一章介紹的Array數組是一個全部對象都共享相同類型的可變序列。比方說Array[String]僅包含String。儘管實例化以後你沒法改變Array的長度。所以,Array是可變的對象。
說到共享相同類型的不可變對象類型,Scala的List類纔是。和數組同樣,List[String]包含的僅僅是String。Scala的List不一樣於Java的java.util.List,老是不可變的(Java的List是可變)。更準確的說法,Scala的List是設計給函數式風格的編程用的。

1List類型定義以及List的特色:

//字符串類型List

scala> val fruit=List("Apple","Banana","Orange")

fruit: List[String] = List(Apple, Banana, Orange)

//前一個語句與下面語句等同

scala> val fruit=List.apply("Apple","Banana","Orange")

fruit: List[String] = List(Apple, Banana, Orange)

//數值類型List

scala> val nums=List(1,2,3,4,5)

nums: List[Int] = List(1, 2, 3, 4, 5)

//多重List,List的子元素爲List

scala> val list = List(List(1, 2, 3), List("adfa", "asdfa", "asdf"))

list: List[List[Any]] = List(List(1, 2, 3), List(adfa, asdfa, asdf))

 

//遍歷List

scala> for(i <- list; from=i; j<-from)println(j)

1

2

3

adfa

asdfa

asdf

2ListArray的區別:

一、List一旦建立,已有元素的值不能改變,可使用添加元素或刪除元素生成一個新的集合返回。
如前面的nums,改變其值的話,編譯器就會報錯。而Array就能夠成功

scala>nums(3)=4

<console>:10: error: value update is not a member of List[Int]

              nums(3)=4

              ^

二、List具備遞歸結構(Recursive Structure),例如鏈表結構
List類型和睦他類型集合同樣,它具備協變性(Covariant),即對於類型S和T,若是S是T的子類型,則List[S]也是List[T]的子類型。
例如:

scala>var listStr:List[Object] = List("This", "Is", "Covariant", "Example")

listStr:List[Object] = List(This, Is, Covariant, Example)

 

//空的List,其類行爲Nothing,Nothing在Scala的繼承層次中的最底層

//,即Nothing是任何Scala其它類型如String,Object等的子類

scala> var listStr = List()

listStr:List[Nothing] = List()

 

scala>var listStr:List[String] = List()

listStr:List[String] = List()

3List經常使用構造方法

//一、經常使用::及Nil進行列表構建

scala> val nums = 1 :: (2:: (3:: (4 :: Nil)))

nums: List[Int] = List(1, 2, 3, 4)

 

 

//因爲::操做符的優先級是從右向左的,所以上一條語句等同於下面這條語句

scala> val nums = 1::2::3::4::Nil

nums:List[Int] = List(1, 2, 3, 4)

至於::操做符的使用將在下面介紹

4List經常使用操做

//判斷是否爲空

scala> nums.isEmpty

res5: Boolean = false

 

//取第一個元素

scala> nums.head

res6: Int = 1

 

//取列表第二個元素

scala>nums.tail.head

res7: Int = 2

 

//取第三個元素

scala>nums.tail.tail.head

res8: Int = 3

 

//插入操做

//在第二個位置插入一個元素

scala>nums.head::(3::nums.tail)

res11: List[Int] = List(1, 3, 2, 3, 4)

 

scala> nums.head::(nums.tail.head::(4::nums.tail.tail))

res12: List[Int] = List(1, 2, 4, 3, 4)

 

//插入排序算法實現

def isort(xs: List[Int]):List[Int] = {

    if(xs.isEmpty) Nil

    else insert(xs.head, issort(xs.tail))

}

 

def insert(x:Int, xs:List[Int]):List[Int] = {

    if(xs.isEmpty || x <= xs.head) x::xs

    else xs.head :: insert(x, xs.tail)

}

 

//鏈接操做

scala>List(1, 2, 3):::List(4, 5, 6)

res13: List[Int] = List(1, 2, 3, 4, 5, 6)

 

//去除最後一個元素外的元素,返回的是列表

scala> nums.init

res13: List[Int] = List(1, 2, 3)

 

//取出列表最後一個元素

scala>nums.last

res14: Int = 4

 

//列表元素倒置

scala> nums.reverse

res15: List[Int] = List(4, 3, 2, 1)

//一些好玩的方法調用

scala> nums.reverse.reverse == nums

//丟棄前面n個元素

scala>nums drop 3

res16: List[Int] = List(4)

//獲取前面n個元素

scala>nums take 1

res17: List[Int] = List[1]

//將列表進行分割

scala> nums.splitAt(2)

res18: (List[Int], List[Int]) = (List(1, 2),List(3, 4))

//前一個操做與下列語句等同

scala> (nums.take(2),nums.drop(2))

res19: (List[Int], List[Int]) = (List(1, 2),List(3, 4))

//Zip操做

scala> val nums=List(1,2,3,4)

nums: List[Int] = List(1, 2, 3, 4)

scala> val chars=List('1','2','3','4')

chars: List[Char] = List(1, 2, 3, 4)

//返回的是List類型的元組(Tuple),返回的元素個數與最小的List集合的元素個數同樣

scala> nums zip chars

res20: List[(Int, Char)] = List((1,1), (2,2), (3,3), (4,4))

 

//List toString方法

scala> nums.toString

res21: String = List(1, 2, 3, 4)

 

//List mkString方法

scala> nums.mkString

res22: String = 1234

 

//轉換成數組

scala> nums.toArray

res23: Array[Int] = Array(1, 2, 3, 4)

5List伴生對象方法

//apply方法

scala>  List.apply(1, 2, 3)

res24: List[Int] = List(1, 2, 3)

//range方法,構建某一值範圍內的List

scala>  List.range(2, 6)

res25: List[Int] = List(2, 3, 4, 5)

//步長爲2

scala>  List.range(2, 6,2)

res26: List[Int] = List(2, 4)

//步長爲-1

scala>  List.range(2, 6,-1)

res27: List[Int] = List()

scala>  List.range(6,2 ,-1)

res28: List[Int] = List(6, 5, 4, 3)

//構建相同元素的List

scala> List.make(5, "hey")

res29: List[String] = List(hey, hey, hey, hey, hey)

 

//unzip方法

scala> List.unzip(res20)

res30: (List[Int], List[Char]) = (List(1, 2, 3, 4),List(1, 2, 3, 4))

 

//list.flatten,將列表平滑成第一個無素

scala> val xss =

     | List(List('a', 'b'), List('c'), List('d', 'e'))

xss: List[List[Char]] = List(List(a, b), List(c), List(d, e))

scala> xss.flatten

res31: List[Char] = List(a, b, c, d, e)

 

//列表鏈接

scala> List.concat(List('a', 'b'), List('c'))

res32: List[Char] = List(a

, b, c)

6:::::操做符介紹

List中經常使用'::',發音爲"cons"。Cons把一個新元素組合到已有元素的最前端,而後返回結果List。

scala> val twoThree = List(2, 3)

scala> val oneTwoThree = 1 :: twoThree

scala> oneTwoThree

oneTwoThree: List[Int] = List(1, 2, 3)

上面表達式"1::twoThree"中,::是右操做數,列表twoThree的方法。可能會有疑惑。表達式怎麼是右邊參數的方法,這是Scala語言的一個例外的狀況:若是一個方法操做符標註,如a * b,那麼方法被左操做數調用,就像a.* (b)--除非方法名以冒號結尾。這種狀況下,方法被右操做數調用。
List有個方法叫":::",用於實現疊加兩個列表。

scala> val one = List('A', 'B')

val one = List('A', 'B')

scala> val two = List('C', 'D')

 

scala> one:::two

res1: List[Char] = List(A, B, C, D)

 

 

 

6.六、Set集合

集是不重複元素的結合。集不保留順序,默認是以哈希集實現。

若是想要按照已排序的順序來訪問集中的元素,可使用SortedSet(已排序數據集),已排序的數據集是用紅黑樹實現的。

默認狀況下,Scala 使用的是不可變集合,若是你想使用可變集合,須要引用 scala.collection.mutable.Set 包。

一、 不可變集合的建立

val set1 =Set("1","1","2","3")
println(set1.mkString(","))

 

二、 可變集合的建立以及添加元素

若是咱們引入的集合的包是可變的,那麼咱們建立的集合就是可變的

import scala.collection.mutable.Set
val set2 = Set(1, 2, 3)
set2.add(4)
set2 += 5
//使用.這個方法添加元素,會返回一個新的集合
val set3 = set2.+(6)
println(set2.mkString(","))
println(set3.mkString("\001"))

 

 

三、可變集合刪除元素

set3 -= 1
println(set3.mkString("."))

set3.remove(2)
println(set3.mkString("."))

 

四、          遍歷Set集合元素

for(x <- set3){
  println(x )
}

 

注意:若是要建立有序的set,那麼須要使用SortedSet。用法與Set相似

更多Set集合操做參見以下:

http://www.runoob.com/scala/scala-sets.html

 

五、Set更多經常使用操做介紹

 

序號

方法

描述

1

def +(elem: A): Set[A]

爲集合添加新元素,並建立一個新的集合,除非元素已存在

2

def -(elem: A): Set[A]

移除集合中的元素,並建立一個新的集合

3

def contains(elem: A): Boolean

若是元素在集合中存在,返回 true,不然返回 false。

4

def &(that: Set[A]): Set[A]

返回兩個集合的交集

5

def &~(that: Set[A]): Set[A]

返回兩個集合的差集

6

def ++(elems: A): Set[A]

合併兩個集合

7

def drop(n: Int): Set[A]]

返回丟棄前n個元素新集合

8

def dropRight(n: Int): Set[A]

返回丟棄最後n個元素新集合

9

def dropWhile(p: (A) => Boolean): Set[A]

從左向右丟棄元素,直到條件p不成立

10

def max: A

查找最大元素

11

def min: A

查找最小元素

12

def take(n: Int): Set[A]

返回前 n 個元素

 

6.七、集合元素與函數的映射

咱們可使用map方法,傳入一個函數,而後將這個函數做用在集合當中的每個元素上面

map:將集合中的每個元素映射到某一個函數

 

 

val listFunc = List("name","age","zhangsan","lisi")
println(listFunc.map(x => x +"hello"))
println(listFunc.map(_.toUpperCase()))

 

flatmap:flat即壓扁,壓平,扁平化,效果就是將集合中的每一個元素的子元素映射到某個函數並返回新的集合

 

val listFunc2 = List("address","phonNo")
println(listFunc2.flatMap( x => x +"WORLD"))

 

 

6.八、隊列Queue

隊列Queue是一個先進先出的結構

一、 建立隊列

//建立可變的隊列
val queue1 = new  mutable.Queue[Int]()
println(queue1)

 

二、隊列當中添加元素

//隊列當中添加元素
queue1 += 1
//隊列當中添加List
queue1 ++=List(2,3,4)
println(queue1)

 

三、按照進入隊列順序,刪除隊列當中的元素(彈出隊列)

val dequeue = queue1.dequeue()
println(dequeue)
println(queue1)

 

四、向隊列當中加入元素(入隊列操做)

//塞入元素到隊列
queue1.enqueue(5,6,7)
println(queue1)

五、獲取第一個與最後一個元素

//獲取第一個元素
println(queue1.head)
//獲取最後一個元素
println(queue1.last)

 

 

6.九、集合當中的化簡、摺疊與掃描操做

一、摺疊、化簡 reduce操做

將二元函數引用集合當中的函數

val reduceList = List(1,2,3,4,5)
//1-2-3-4-5  = -13
val reduceLeftList = reduceList.reduceLeft(_ - _)
val reduceLeftList2 = reduceList.reduceLeft((x,y) => x-y)
println(reduceLeftList)
println(reduceLeftList2)
//reduceRight操做
// 4-5 = -1
// 3- (-1) = 4
//2-4 = -2
//1 -(-2) = 3
val reduceRightList = reduceList.reduceRight(_ - _)
println(reduceRightList)

 

二、 摺疊、化簡folder操做

fold函數將上一步返回的值做爲函數的第一個參數繼續傳遞參與運算,直到list中的全部元素被遍歷。能夠把reduceLeft看作簡化版的foldLeft。相關函數:fold,foldLeft,foldRight,能夠參考reduce的相關方法理解。

reduce的本質其實就是fold操做,只不過咱們使用fold操做的時候,須要指定初始值

fold操做

val  foldList = List(1,9,2,8)
val foldResult = foldList.fold(10)((x,y) => x+y)
println(foldResult)

 

foldLeft操做

 

//50-1-9-2-8 = 30
val foldLeftResult = foldList.foldLeft(50)((x,y) => x-y)
println(foldLeftResult)

 

6.十、拉鍊操做

對於多個List集合,咱們可使用Zip操做,將多個集合當中的值綁定到一塊兒去

val  zipList1 = List("name","age","sex")
val zipList2 = List("zhangsan",28)
val zip = zipList1.zip(zipList2)
val toMap1 = zip.toMap
println(zip)
println(toMap1)

 

6.十一、迭代器

對於集合當中的元素,咱們也可使用迭代器來進行遍歷

val listIterator = List(1,2,"zhangsan")
val iterator = listIterator.iterator
while(iterator.hasNext){
  println(iterator.next())
}

 

 

6.十二、線程安全的集合

scala當中爲了解決多線程併發的問題,提供對應的線程安全的集合

https://www.scala-lang.org/api/2.11.8/#package

SynchronizedBuffer

SynchronizedMap

SynchronizedPriorityQueue

SynchronizedQueue

SynchronizedSet

 

6.1三、操做符

大體瞭解便可

1)  若是想在變量名、類名等定義中使用語法關鍵字(保留字),能夠配合反引號反引號:

val `val` = 42

2)  這種形式叫中置操做符,A操做符B等同於A.操做符(B)

3)  後置操做符,A操做符等同於A.操做符,若是操做符定義的時候不帶()則調用時不能加括號

4)  前置操做符,+、-、!、~等操做符A等同於A.unary_操做符。

5)  賦值操做符,A操做符=B等同於A=A操做符B

 

七、  高階函數

7.一、做爲參數的函數

函數能夠做爲一個參數傳入到一個方法當中去

def main(args: Array[String]): Unit = {
 val myFu(x:Int) =>{
   x * x
 }nc1 =
 val myArray = Array(1,3,5,7,9).map(myFunc1)
  println(myArray.mkString(","))
}

 

7.二、匿名函數

沒有名字的函數便是匿名函數,咱們能夠經過函數表達式來設置匿名函數

def main(args: Array[String]): Unit = {
  println((x:Int,y:String) => x + y)
}

 

7.三、高階函數

一、可以接受函數做爲參數的方法,叫作高階函數

 

def main(args: Array[String]): Unit = {
  val func3:(Int,String) =>(String,Int)={
    (x,y)=>(y,x)
  }
  def myMethod3(hello:(Int,String) => (String,Int)):Int ={
    val resultFunc = hello(20,"hello")
    resultFunc._2
  }
  println(myMethod3(func3))
}

二、高階函數一樣能夠返回一個函數類型

def main(args: Array[String]): Unit = {
  def myFunc4(x:Int) = (y:String) => y
  println(myFunc4(50))
}

7.四、參數類型推斷

def main(args: Array[String]): Unit = {
  val array  = Array(1,2,3,4,5,6,7,8,9)
  //map當中須要傳入一個函數,咱們能夠直接定義一個函數
 
array.map((x:Int) => x * 2 )
  //進一步化簡 參數推斷省去類型信息
 
array.map((x) => x * 2 )
  //進一步化簡  單個參數能夠省去括號
 
array.map( x => x * 2 )
  //進一步化簡  若是變量在=>右邊只出現一次,能夠用_來代替
 
array.map( 2 * _ )

}

 

7.五、閉包與柯里化

柯里化存在的意義是什麼???注意:柯里化是scala當中面向函數式編程致使的一種必然的結果,最終推導而來產生的一種現象

def main(args: Array[String]): Unit = {
  //柯里化的定義形式
 
def kery(x:Int)(y:Int):Int={
    x + y
  }
  println(kery(3)(5))
  //柯里化的推導過程,注意方法的返回值不要定義任何的返回值類型

val keryResult = (x:Int) => (y:Int) => {x + y}


 
def keryMethod(x:Int) ={
    (y:Int) => x+ y
  }
  println(keryMethod(20))
  println(keryMethod(20)(10))
  //注意,方法當中的函數,調用了方法的參數,就叫作閉包
}

 

再來看一個案例

/**
  *
柯里化的應用,比較數組集合當中兩個對應下標字符串是否相等
  *
@param args
 
*/
def main(args: Array[String]): Unit = {
  val a = Array("Hello", "World")
  val b = Array("hello", "world")
  println(a.corresponds(b)(_.equalsIgnoreCase(_)))

}

 

 

八、scala當中的類

一、類的定義與建立

建立一個scala class來定義咱們的一個類。類當中能夠定義各類屬性或者方法,或者函數均可以

class Person {
  //定義一個屬性,叫作name的,使用val不可變量來進行修飾
  // 用val修飾的變量是可讀屬性,有getter但沒有setter(至關與Java中用final修飾的變量)
  val name:String ="zhangsan"
 
//定義一個屬性,叫作age的,使用var可變量來進行修飾
  //用var修飾的變量都既有getter,又有setter
  var age:Int = 28

  //類私有字段,只能在類的內部使用或者伴生對象中訪問
  private val  address:String = "地球上"

 
//類私有字段,訪問權限更加嚴格的,該字段在當前類中被訪問
  //在伴生對象裏面也不能夠訪問
  private[this] var pet = "小強"

 
//在類當中定義了一個方法,
  def hello(first:Int,second:String):Int ={
    println(first+"\t"+second)
    250
  }
  /**
    * 定義了一個函數
    */
  val func1 =(x:Int,y:Int) =>{
    x+y
  }
}

class Person{


  //定義屬性
  val name:String="zhangsan"

  var age:Int=15

  //定義類私有字段,只能在本類或者伴生對象中使用
  private var addres="beijing"


  def printAddres(): Unit ={
    println(this.addres)
    println(gender)
  }
  //定義對象私有的字段,訪問只能在本類中
  private [this] var gender:String="nv"

  def testPrivateThis(other:Person): Unit ={
//    this.gender.equals(other.gender)
  }


}
//同名且在同一個文件中,
object Person{
  def main(args: Array[String]): Unit = {
    val person = new Person
    person.addres

  }
}


object ClassDemo01{
  def main(args: Array[String]): Unit = {
    val p1 = new Person
    val p2 = new Person()
    p1.age=10
    p1.age=(20)
    p1.age_=(30)
    p1.name
  }
}

 

 

 

二、類的實例化以及使用

若是想要使用類的話,那麼REPL就知足不了咱們的要求了,咱們從新建立一個對應的Object的scala文件

object ScalaClass {
  def main(args: Array[String]): Unit = {
    //建立對象兩種方式。這裏都是使用的無參構造來進行建立對象的
   
val person = new Person
    val person1 = new Person()
    //注意,咱們可使用對象的屬性加上_= 給var修飾的屬性進行從新賦值
    //
其實就是調用set方法,方法名叫作 age_=   
   
person.age_= (50)
    //直接調用類的屬性,其實就是調用get方法
    println
(person.age)
    println(person.hello(50,"helloworld"))
    val func = person.func1(10,20)
    println(func)
    println("============")
  }
}

三、屬性的getter和setter方法

對於scala類中的每個屬性,編譯後,會有一個私有的字段和相應的getter、setter方法生成

 

//getter方法
println(person age)
//setter方法
println(person age_= (18))
//getter方法
println(person.age)

 

 

固然了,你也能夠不使用自動生成的方式,本身定義getter和setter方法

class Dog2 {

  private var _leg = 4

  def leg = _leg

  def leg_=(newLeg: Int) {

    _leg = newLeg

  }

}

使用之:

val dog2 = new Dog2

dog2.leg_=(10)

println(dog2.leg)

規範提示:本身手動建立變量的getter和setter方法須要遵循如下原則:

1) 字段屬性名以「_」做爲前綴,如:_leg

2) getter方法定義爲:def leg = _leg

3) setter方法定義時,方法名爲屬性名去掉前綴,並加上後綴,後綴是:「leg_=」,如例子所示

 

四、類的構造器

scala當中類的構造器分爲兩種:主構造器和輔助構造器

scala當中規定,全部的輔助構造器,最後都必須調用另一個構造器,另一個構造器能夠是輔助構造器,也能夠是主構造器

//主構造器,直接定義在類上面
class Dog (name:String,age:Int){

  //在scala當中,能夠直接將代碼寫在class當中,而在java當中,
  //代碼必須包含在方法當中。
  //其實在scala當中,雖然你把代碼寫在了Class類當中,通過編譯以後,
  //class類的代碼都進入到了主構造器方法當中去了
  println(name)
  println(age)

  var gender:String = "";

   def this(name:String,age:Int,gender:String){
    //每一個輔助構造器,都必須以其餘輔助構造器,或者主構造器的調用做爲第一句
    this(name:String,age:Int)
    this.gender = gender
  }

  var color ="";

  /**
    * 咱們也能夠經過private來進行修飾咱們的構造器,
    * @param name
   
* @param age
   
* @param color
   
* @param gender
   
*/
  private def this(name:String,age:Int,color:String,gender:String){
    this(name:String,age:Int)
    this.color = color
  }

}

 

 

九、scala當中的對象

一、scala當中的Object

在scala當中,沒有相似於像java當中的static修飾的靜態屬性或者靜態方法或者靜態代碼塊之類的,可是咱們能夠經過scala當中的Object來實現相似的功能。能夠理解爲scala當中的Object裏面的屬性或者方法都是靜態的,能夠直接調用

定義一個class類,而後在class類當中定義一個Object的對象。object對象當中的全部屬性或者方法都是靜態的

class Session {
  def hello(first:Int):Int={
    println(first)
    first
  }
}
object SessionFactory{
  val session = new Session
  def getSession():Session ={
    session
 
}
  def main(args: Array[String]): Unit = {

    for(x <- 1 to 10){
      //經過直接調用,產生的對象都是單列的
     
val session = SessionFactory.getSession()
      println(session)
    }
  }
}

 

二、伴生類與伴生對象

  • 若是有一個class文件,還有一個與class同名的object文件,那麼就稱這個object是class的伴生對象,class是object的伴生類;
  • 伴生類和伴生對象必須存放在一個.scala文件中;
  • 伴生類和伴生對象的最大特色是,能夠相互訪問;
  • 舉例說明:

class ClassObject {
  val id = 1
  private var name = "itcast"
  def
printName(): Unit ={
    //在Dog類中能夠訪問伴生對象Dog的私有屬性
    println
(ClassObject.CONSTANT + name )
  }


}

object ClassObject{
  //伴生對象中的私有屬性
 
private val CONSTANT = "汪汪汪 : "
  def
main(args: Array[String]) {
    val p = new ClassObject
    //訪問私有的字段name
   
p.name = "123"
   
p.printName()
  }
}

 

 

三、scala當中的apply方法

  • object 中很是重要的一個特殊方法,就是apply方法;
  • apply方法一般是在伴生對象中實現的,其目的是,經過伴生類的構造函數功能,來實現伴生對象的構造函數功能;
  • 一般咱們會在類的伴生對象中定義apply方法,當遇到類名(參數1,...參數n)時apply方法會被調用;
  • 在建立伴生對象或伴生類的對象時,一般不會使用new class/class() 的方式,而是直接使用 class(),隱式的調用伴生對象的 apply 方法,這樣會讓對象建立的更加簡潔;
  • 舉例說明:

class ApplyObjectClass (name:String){
  println(name)
}
object ApplyObjectClass{
 
  def apply(name:String): ApplyObjectClass = {
    new ApplyObjectClass(name)
  }
  def main(args: Array[String]): Unit = {
    //調用的apply方法來建立對象
   
val applyObjectClass = ApplyObjectClass("lisi")
    //調用的是new  Class來建立對象
   
val applyObjectClass2 =new ApplyObjectClass("wangwu")
  }
}

 

四、scala當中的main方法

 

  • 同Java同樣,若是要運行一個程序,必需要編寫一個包含 main 方法的類;
  • 在 Scala 中,也必需要有一個 main 方法,做爲入口;
  • Scala 中的 main 方法定義爲 def main(args: Array[String]),並且必須定義在 object 中;
  • 除了本身實現 main 方法以外,還能夠繼承 App Trait,而後,將須要寫在 main 方法中運行的代碼,直接做爲 object 的 constructor 代碼便可,並且還可使用 args 接收傳入的參數;
  • 案例說明:

 

//1.在object中定義main方法
object Main_Demo1 {
  def main(args: Array[String]) {
    if(args.length > 0){
      println("Hello, " + args(0))
    }else{
      println("Hello World1!")
    }
  }
}
//2.使用繼承App Trait ,將須要寫在 main 方法中運行的代碼
//
直接做爲 object 的 constructor 代碼便可,
//
並且還可使用 args 接收傳入的參數。

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

 

五、枚舉

Scala中沒有枚舉類型,可是咱們能夠退經過定義一個擴展Enumeration類的對象,並以value調用初始化枚舉中的全部可能值:

 

十、scala當中的繼承

一、Scala中繼承(extends)的概念

  • Scala 中,讓子類繼承父類,與 Java 同樣,也是使用 extends 關鍵字;
  • 繼承就表明,子類可繼承父類的 field 和 method ,而後子類還能夠在本身的內部實現父類沒有的,子類特有的 field 和method,使用繼承能夠有效複用代碼;
  • 子類能夠覆蓋父類的 field 和 method,可是若是父類用 final 修飾,或者 field 和 method 用 final 修飾,則該類是沒法被繼承的,或者 field 和 method 是沒法被覆蓋的。
  • private 修飾的 field 和 method 不能夠被子類繼承,只能在類的內部使用;
  • field 必需要被定義成 val 的形式才能被繼承,而且還要使用 override 關鍵字。 由於 var 修飾的 field 是可變的,在子類中可直接引用被賦值,不須要被繼承;即 val 修飾的才容許被繼承,var 修飾的只容許被引用。繼承就是改變、覆蓋的意思。
  • Java 中的訪問控制權限,一樣適用於 Scala

 

類內部

本包

子類

外部包

 

 

 

 

 

public

 

 

 

 

 

protected

×

 

 

 

 

 

default

×

×

 

 

 

 

 

private

×

×

×

 

 

 

 

 

 

  • 舉例說明:
package cn.itcast.extends_demo

class Person {
  val name="super"
  def getName=this.name
}
class Student extends Person{
  //繼承加上關鍵字
  override
  val
name="sub"
  //子類能夠定義本身的field和method
  val score="A"
  def getScore=this.score
}

 

二、Scala中override 和 super 關鍵字

  • Scala中,若是子類要覆蓋父類中的一個非抽象方法,必需要使用 override 關鍵字;子類能夠覆蓋父類的 val 修飾的field,只要在子類中使用 override 關鍵字便可。
  • override 關鍵字能夠幫助開發者儘早的發現代碼中的錯誤,好比, override 修飾的父類方法的方法名拼寫錯誤。
  • 此外,在子類覆蓋父類方法後,若是在子類中要調用父類中被覆蓋的方法,則必需要使用 super 關鍵字,顯示的指出要調用的父類方法。
  • 舉例說明:

class Person1 {

  private val name = "leo"

  val age=50

  def getName = this.name

}

class Student1 extends Person1{

  private val score = "A"

  //子類能夠覆蓋父類的 val field,使用override關鍵字

  override

  val age=30

  def getScore = this.score

  //覆蓋父類非抽象方法,必需要使用 override 關鍵字

  //同時調用父類的方法,使用super關鍵字

  override def getName = "your name is " + super.getName

}

三、Scala中isInstanceOf 和 asInstanceOf

若是實例化了子類的對象,可是將其賦予了父類類型的變量,在後續的過程當中,又須要將父類類型的變量轉換爲子類類型的變量,應該如何作?

 

  • 首先,須要使用 isInstanceOf 判斷對象是否爲指定類的對象,若是是的話,則可使用 asInstanceOf 將對象轉換爲指定類型;
  • 注意: p.isInstanceOf[XX] 判斷 p 是否爲 XX 對象的實例;p.asInstanceOf[XX] 把 p 轉換成 XX 對象的實例
  • 注意:若是沒有用 isInstanceOf 先判斷對象是否爲指定類的實例,就直接用 asInstanceOf 轉換,則可能會拋出異常;
  • 注意:若是對象是 null,則 isInstanceOf 必定返回 false, asInstanceOf 必定返回 null;
  • Scala與Java類型檢查和轉換

 

Scala

Java

obj.isInstanceOf[C]

obj instanceof C

obj.asInstanceOf[C]

(C)obj

classOf[C]

C.class

 

  • 舉例說明:

 

package cn.itcast.extends_demo

class Person3 {}
class Student3 extends Person3
object Student3{
    def main (args: Array[String] ) {
    val p: Person3 = new Student3
    var s: Student3 = null
   
//若是對象是 null,則 isInstanceOf 必定返回 false
    println (s.isInstanceOf[Student3])
    // 判斷 p 是否爲 Student3 對象的實例
  if (p.isInstanceOf[Student3] ) {
    //把 p 轉換成 Student3 對象的實例
      s = p.asInstanceOf[Student3]
  }
  println (s.isInstanceOf[Student3] )
  }
}

 

四、Scala中getClass 和 classOf

  • isInstanceOf 只能判斷出對象是否爲指定類以及其子類的對象,而不能精確的判斷出,對象就是指定類的對象;
  • 若是要求精確地判斷出對象就是指定類的對象,那麼就只能使用 getClass 和 classOf 了;
  • p.getClass 能夠精確地獲取對象的類,classOf[XX] 能夠精確的獲取類,而後使用 == 操做符便可判斷;
  • 舉例說明:
package cn.itcast.extends_demo

class Person4 {}
class Student4 extends Person4
object Student4{
  def main(args: Array[String]) {
    val p:Person4=new Student4
    //判斷p是否爲Person4類的實例
    println(p.isInstanceOf[Person4])//true
    //判斷p的類型是否爲Person4類
    println(p.getClass == classOf[Person4])//false
    //判斷p的類型是否爲Student4類
    println(p.getClass == classOf[Student4])//true
  }
}

 

五、Scala中使用模式匹配進行類型判斷

  • 在實際的開發中,好比 spark 源碼中,大量的地方使用了模式匹配的語法進行類型的判斷,這種方式更加地簡潔明瞭,並且代碼的可維護性和可擴展性也很是高;

 

  • 使用模式匹配,功能性上來講,與 isInstanceOf 的做用同樣,主要判斷是否爲該類或其子類的對象便可,不是精準判斷。
  • 等同於 Java 中的 switch case 語法;
  • 舉例說明:
package cn.itcast.extends_demo

class Person5 {}
class Student5 extends Person5
object Student5{
  def main(args: Array[String]) {
    val p:Person5=new Student5
    p match {
      // 匹配是否爲Person類或其子類對象
      case per:Person5 => println("This is a Person5's Object!")
      // 匹配全部剩餘狀況
      case _  =>println("Unknown type!")
    }
  }
}

 

六、Scala中protected

  • 跟 Java 同樣,Scala 中一樣可以使用 protected 關鍵字來修飾 field 和 method。在子類中,可直接訪問父類的 field 和 method,而不須要使用 super 關鍵字;

 

  • 還可使用 protected[this] 關鍵字, 訪問權限的保護範圍:只容許在當前子類中訪問父類的 field 和 method,不容許經過其餘子類對象訪問父類的 field 和 method。

 

  • 舉例說明:
package cn.itcast.extends_demo

class Person6{
  protected var name:String="tom"
  protected[this] var hobby:String ="game"
  protecteddef sayBye=println("再見...")
}
class Student6 extends Person6{
  //父類使用protected 關鍵字來修飾 field能夠直接訪問
  def  sayHello =println("Hello "+name)
  //父類使用protected 關鍵字來修飾method能夠直接訪問
  def  sayByeBye=sayBye
  def makeFriends(s:Student6)={
    println("My hobby is "+hobby+", your hobby is UnKnown")
  }
}
object Student6{
  def main(args: Array[String]) {
    val s:Student6=new Student6
    s.sayHello
    s.makeFriends(s)
    s.sayByeBye
  }
}

 

七、Scala中調用父類的constructor

  • Scala中,每一個類均可以有一個主constructor和任意多個輔助constructor,並且每一個輔助constructor的第一行都必須調用其餘輔助constructor或者主constructor代碼;所以子類的輔助constructor是必定不可能直接調用父類的constructor的;

 

  • 只能在子類的主constructor中調用父類的constructor。
  • 若是父類的構造函數已經定義過的 field,好比name和age,子類再使用時,就不要用 val 或 var 來修飾了,不然會被認爲,子類要覆蓋父類的field,且要求必定要使用 override 關鍵字。
  • 舉例說明:
package cn.itcast.extends_demo

class Person7(val name:String,val age:Int){
  var score :Double=0.0
  var address:String="beijing"
  def this(name:String,score:Double)={
    //每一個輔助constructor的第一行都必須調用其餘輔助constructor或者主constructor代碼
    //主constructor代碼
      this(name,30)
      this.score=score
  }
  //其餘輔助constructor
  def this(name:String,address:String)={
      this(name,100.0)
      this.address=address
  }
}
class Student7(name:String,score:Double) extends Person7(name,score)

 

八、Scala中抽象類

  • 若是在父類中,有某些方法沒法當即實現,而須要依賴不一樣的子類來覆蓋,重寫實現不一樣的方法。此時,能夠將父類中的這些方法編寫成只含有方法簽名,不含方法體的形式,這種形式就叫作抽象方法;
  • 一個類中,若是含有一個抽象方法或抽象field,就必須使用abstract將類聲明爲抽象類,該類是不能夠被實例化的;
  • 在子類中覆蓋抽象類的抽象方法時,能夠不加override關鍵字;
  • 舉例說明:
package cn.itcast.extends_demo

abstract class Person9(val name:String) {
  //必須指出返回類型,否則默認返回爲Unit
 
def sayHello:String
  def sayBye:String
}
classStudent9(name:String) extends Person9(name){
  //必須指出返回類型,否則默認
  def sayHello: String = "Hello,"+name
  def sayBye: String ="Bye,"+name
}
object Student9{
  def main(args: Array[String]) {
    val s = new Student9("tom")
    println(s.sayHello)
    println(s.sayBye)
  }
}

 

 

 

九、Scala中抽象field

  • 若是在父類中,定義了field,可是沒有給出初始值,則此field爲抽象field;
  • 舉例說明:
package cn.itcast.extends_demo

abstract class Person10 (val name:String){
//抽象fields
    val age:Int
}
class Student10(name: String) extends Person10(name) {
   val age: Int = 50
}
 

 

 

 

 

十一、scala當中的特質trait

一、將trait做爲接口使用

  • Scala中的trait是一種特殊的概念;
  • 首先先將trait做爲接口使用,此時的trait就與Java中的接口 (interface)很是相似;
  • 在trait中能夠定義抽象方法,就像抽象類中的抽象方法同樣,只要不給出方法的方法體便可;
  • 類可使用extends關鍵字繼承trait,注意,這裏不是 implement,而是extends ,在Scala中沒有 implement 的概念,不管繼承類仍是trait,統一都是 extends;
  • 類繼承後,必須實現其中的抽象方法,實現時,不須要使用 override 關鍵字;
  • Scala不支持對類進行多繼承,可是支持多重繼承 trait,使用 with 關鍵字便可。
  • 舉例說明:
  • Scala中的trait不只能夠定義抽象方法,還能夠定義具體的方法,此時 trait 更像是包含了通用方法的工具,能夠認爲trait還包含了類的功能。
  • 舉例說明:
  • Scala 中的 trait 能夠定義具體的 field,此時繼承 trait 的子類就自動得到了 trait 中定義的 field;
  • 可是這種獲取 field 的方式與繼承 class 的是不一樣的。 若是是繼承 class 獲取的 field ,實際上仍是定義在父類中的;而繼承 trait獲取的 field,就直接被添加到子類中了。
  • 舉例說明:
package cn.itcast.triat

trait HelloTrait {
  def sayHello(): Unit
}
trait MakeFriendsTrait {
  def makeFriends(c: Children): Unit
}
//多重繼承 trait
class Children(val name: String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable{
  def sayHello() =println("Hello, " + this.name)
  def makeFriends(c: Children) = println("Hello, my name is " + this.name + ", your name is " + c.name)
}
object Children{
  def main(args: Array[String]) {
    val c1=new Children("tom")
    val c2=new Children("jim")
    c1.sayHello()//Hello, tom
    c1.makeFriends(c2)//Hello, my name is tom, your name is jim
  }
}

二、在trait中定義具體的方法

package cn.itcast.triat
/**
 *
好比 trait 中能夠包含不少子類都通用的方法,例如打印日誌或其餘工具方法等等。
 
* spark就使用trait定義了通用的日誌打印方法;
 
*/
trait Logger {
  def log(message: String): Unit = println(message)
}
class PersonForLog(val name: String) extends Logger {
  def makeFriends(other: PersonForLog) = {
    println("Hello, " + other.name + "! My name is " + this.name + ", I miss you!!")
    this.log("makeFriends method is invoked with parameter PersonForLog[name = " + other.name + "]")
  }
}
object PersonForLog{
  def main(args: Array[String]) {
    val p1=new PersonForLog("jack")
    val p2=new PersonForLog("rose")
    p1.makeFriends(p2)
    //Hello, rose! My name is jack, I miss you!!
    //makeFriens method is invoked with parameter PersonForLog[name = rose]
  }
}

三、在trait中定義具體field

 

package cn.itcast.triat

trait PersonForField {
  val  age:Int=50
}

//繼承 trait 獲取的field直接被添加到子類中
class StudentForField(val name: String) extends PersonForField {
  def sayHello = println("Hi, I'm " + this.name + ", my  age  is "+ age)
}

object StudentForField{
  def main(args: Array[String]) {
    val s=new StudentForField("tom")
    s.sayHello
  }
}
 

 

四、在trait中定義抽象field

  • Scala中的trait也能定義抽象field, 而trait中的具體方法也能基於抽象field編寫;
  • 繼承trait的類,則必須覆蓋抽象field,提供具體的值;
  • 舉例說明:
package cn.itcast.triat

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

class PersonForAbstractField(val name: String) extends SayHelloTrait {
  //必須覆蓋抽象 field
  val msg = "Hello"
  def makeFriends(other: PersonForAbstractField) = {
    this.sayHello(other.name)
    println("I'm " + this.name + ", I want to make friends with you!!")
  }
}
object PersonForAbstractField{
  def main(args: Array[String]) {
    val p1=new PersonForAbstractField("Tom")
    val p2=new PersonForAbstractField("Rose")
    p1.makeFriends(p2)
  }
}

 

五、在實例對象指定混入某個trait

  • 可在建立類的對象時,爲該對象指定混入某個trait,且只有混入了trait的對象才具備trait中的方法,而其餘該類的對象則沒有;
  • 在建立對象時,使用 with 關鍵字指定混入某個 trait; 
  • 舉例說明:
  • Scala中支持讓類繼承多個trait後,可依次調用多個trait中的同一個方法,只要讓多個trait中的同一個方法,在最後都依次執行 super 關鍵字便可;
  • 類中調用多個trait中都有的這個方法時,首先會從最右邊的trait的方法開始執行,而後依次往左執行,造成一個調用鏈條;
  • 這種特性很是強大,其實就是設計模式中責任鏈模式的一種具體實現;
  • 案例說明:
package cn.itcast.triat

trait LoggedTrait {
  // 該方法爲實現的具體方法
  def log(msg: String) = {}
}
trait MyLogger extends LoggedTrait{
  // 覆蓋 log() 方法
override def log(msg: String) = println("log: " + msg)
}

class PersonForMixTraitMethod(val name: String) extends LoggedTrait {
  def sayHello = {
    println("Hi, I'm " + this.name)
    log("sayHello method is invoked!")
  }
}
object PersonForMixTraitMethod{
  def main(args: Array[String]) {
    val tom= new PersonForMixTraitMethod("Tom").sayHello //結果爲:Hi, I'm Tom
    // 使用 with 關鍵字,指定混入MyLogger trait
    val rose = new PersonForMixTraitMethod("Rose") with MyLogger
    rose.sayHello
// 結果爲:     Hi, I'm Rose
// 結果爲:     log: sayHello method is invoked!
  }
}

六、trait 調用鏈

package cn.itcast.triat

trait HandlerTrait {
  def handle(data: String) = {println("last one")}
}
trait DataValidHandlerTrait extends HandlerTrait {
  override def handle(data: String) = {
              println("check data: " + data)
              super.handle(data)
}
}
trait SignatureValidHandlerTrait extends HandlerTrait {
  override def handle(data: String) = {
          println("check signature: " + data)
          super.handle(data)
  }
}
class PersonForRespLine(val name: String) extends SignatureValidHandlerTrait with DataValidHandlerTrait {
  def sayHello = {
        println("Hello, " + this.name)
        this.handle(this.name)
  }
}
object PersonForRespLine{
  def main(args: Array[String]) {
     val p=new PersonForRespLine("tom")
      p.sayHello
      //執行結果:
//    Hello, tom
//    check data: tom
//    check signature: tom
//    last one
  }
}

 

 

七、混合使用 trait 的具體方法和抽象方法

  • 在 trait 中,能夠混合使用具體方法和抽象方法;
  • 可讓具體方法依賴於抽象方法,而抽象方法則可放到繼承 trait的子類中去實現;
  • 這種 trait 特性,其實就是設計模式中的模板設計模式的體現;
  • 舉例說明:
package cn.itcast.triat

trait ValidTrait {
 //抽象方法
  def getName: String    
//具體方法,具體方法的返回值依賴於抽象方法
                        
 def valid: Boolean = {"Tom".equals(this.getName)
  }
}
class PersonForValid(val name: String) extends ValidTrait {
  def getName: String = this.name
}

object PersonForValid{
  def main(args: Array[String]): Unit = {
    val person = new PersonForValid("Rose")
    println(person.valid)
  }
}

 

 

八、trait的構造機制

  • 在Scala中,trait也是有構造代碼的,即在trait中,不包含在任何方法中的代碼;
  • 繼承了trait的子類,其構造機制以下:
  • 父類的構造函數先執行, class 類必須放在最左邊;多個trait從左向右依次執行;構造trait時,先構造父 trait,若是多個trait繼承同一個父trait,則父trait只會構造一次;全部trait構造完畢以後,子類的構造函數最後執行。
  • 舉例說明:

 

 

package cn.itcast.triat

class Person_One {
  println("Person's constructor!")
}
trait Logger_One {
  println("Logger's constructor!")
}
trait MyLogger_One extends Logger_One {
  println("MyLogger's constructor!")
}
trait TimeLogger_One extends Logger_One {
  println("TimeLogger's contructor!")
}
class Student_One extends Person_One with MyLogger_One with TimeLogger_One {
  println("Student's constructor!")
  }
object exe_one {
  def main(args: Array[String]): Unit = {
    val student = new Student_One
    //執行結果爲:
//      Person's constructor!
//      Logger's constructor!
//      MyLogger's constructor!
//      TimeLogger's contructor!
//      Student's constructor!
  }
}

 

九、trait 繼承 class

  • 在Scala中trait 也能夠繼承 class,此時這個 class 就會成爲全部繼承該 trait 的子類的超級父類。
  • 舉例說明:
package cn.itcast.triat

class MyUtil {
  def printMsg(msg: String) = println(msg)
}
trait Logger_Two extends MyUtil {
  def log(msg: String) = this.printMsg("log: " + msg)
}
class Person_Three(val name: String) extends Logger_Two {
    def sayHello {
        this.log("Hi, I'm " + this.name)
        this.printMsg("Hello, I'm " + this.name)
  }
}
object Person_Three{
  def main(args: Array[String]) {
      val p=new Person_Three("Tom")
      p.sayHello
    //執行結果:
//      log: Hi, I'm Tom
//      Hello, I'm Tom
  }
}

 

 

 

十二、模式匹配和樣例類

 

Scala有一個十分強大的模式匹配機制,能夠應用到不少場合:如switch語句、類型檢查等。而且Scala還提供了樣例類,對模式匹配進行了優化,能夠快速進行匹配。

一、字符匹配

def main(args: Array[String]): Unit = {
  val charStr = '6'
 
charStr match {
    case '+' => println("匹配上了加號")
    case '-' => println("匹配上了減號")
    case '*' => println("匹配上了乘號")
    case '/' => println("匹配上了除號")
      //注意。全部的模式匹配都必須最終匹配上一個值,若是沒有匹配上任何值,就會報錯
   // case _  => println("
都沒有匹配上,我是默認值")
 
}
}

 

二、 匹配字符串

def main(args: Array[String]): Unit = {
  val arr = Array("hadoop", "zookeeper", "spark")
  val name = arr(Random.nextInt(arr.length))
  name match {
    case "hadoop"    => println("大數據分佈式存儲和計算框架...")
    case "zookeeper" => println("大數據分佈式協調服務框架...")
    case "spark" => println("大數據分佈式內存計算框架...")
    case _ => println("我不認識你...")
  }
}

三、守衛

模式匹配當中,咱們也能夠經過條件進行判斷

 

def main(args: Array[String]): Unit = {
  var ch = "500"
    var
sign = 0
    ch match {
      case "+" => sign = 1
      case "-" => sign = 2
      case _ if ch.equals("500") => sign = 3
      case _ => sign = 4
    }
    println(ch + " " + sign)
}

四、 匹配類型

注意在map當中會存在泛型擦除的狀況。注意在進行非數組的類型匹配的時候,類型都會進行擦除

def main(args: Array[String]): Unit = {
  //注意泛型擦除,在模式匹配當中的類型匹配中,除了Array類型覺得,全部的其餘的數據類型都會被擦除掉
 
val a = 3
  val obj = if(a == 1) 1
  else if(a == 2) "2"
  else if
(a == 3) BigInt(3)
  else if(a == 4) Map("aa" -> 1)
  else if(a == 5) Map(1 -> "aa")
  else if(a == 6) Array(1, 2, 3)
  else if(a == 7) Array("aa", 1)
  else if(a == 8) Array("aa")
  val r1 = obj match {
    case x: Int => x
    case s: String => s.toInt
    case BigInt => -1 //不能這麼匹配
   
case _: BigInt => Int.MaxValue
   
case m: Map[String, Int] => "Map[String, Int]類型的Map集合"
    case
m: Map[_, _] => "Map集合"
    case
a: Array[Int] => "It's an Array[Int]"
    case
a: Array[String] => "It's an Array[String]"
    case
a: Array[_] => "It's an array of something other than Int"
    case
_ => 0
  }
  println(r1 + ", " + r1.getClass.getName)

}

 

 

五、匹配數組、元組、集合

def main(args: Array[String]): Unit = {
  val arr = Array(0, 3, 5)
  arr match {
    case Array(0, x, y) => println(x + " " + y)
    case Array(0) => println("only 0")
      //匹配數組以1 開始做爲第一個元素
   
case Array(1, _*) => println("0 ...")
    case _ => println("something else")
  }

  val lst = List(3, -1)
  lst match {
    case 0 :: Nil => println("only 0")
    case x :: y :: Nil => println(s"x: $x y: $y")
    case 0 :: tail => println("0 ...")
    case _ => println("something else")
  }

  val tup = (1, 3, 7)
  tup match {
    case (1, x, y) => println(s"1, $x , $y")
    case (_, z, 5) => println(z)
    case  _ => println("else")
  }

}

 

注意:在Scala中列表要麼爲空(Nil表示空列表)要麼是一個head元素加上一個tail列表。

9 :: List(5, 2)  :: 操做符是將給定的頭和尾建立一個新的列表

注意::: 操做符是右結合的,如9 :: 5 :: 2 :: Nil至關於 9 :: (5 :: (2 :: Nil))

六、 樣例類

樣例類首先是類,除此以外它是爲模式匹配而優化的類,樣例類用case關鍵字進行聲明。樣例類主要是使用在咱們後面的sparkSQL當中,經過樣例類來映射咱們的表當中的對象

定義形式:

case class 類型,是多例的,後面要跟構造參數。 case class Student(name:String)

case object 類型,是單例的。  case object Person

case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
case object CheckTimeOutTask
//1、樣例類當中的主構造器參數默認爲val
//2
、樣例類當中的apply和unapply方法自動生成
object CaseDemo04 extends App {
  val arr = Array(CheckTimeOutTask, HeartBeat(12333), SubmitTask("0001", "task-0001"))
  arr(2) match {
    case SubmitTask(id, name) => {
      println(s"$id,$name")
      println(id)
      println(name)
      println(id+"\t"+name)
    }
    case HeartBeat(time) => {
      println(time)
    }
    case CheckTimeOutTask => {
      println("check")
    }
  }
}

七、偏函數

被包在花括號內沒有match的一組case語句是一個偏函數,它是PartialFunction[A, B]的一個實例,A表明輸入參數類型,B表明返回結果類型,經常使用做輸入模式匹配,偏函數最大的特色就是它只接受和處理其參數定義域的一個子集。

val func1: PartialFunction[String, Int] = {
  case "one" => 1
  case "two" => 2
 // case _ => -1
}

def func2(num: String) : Int = num match {
  case "one" => 1
  case "two" => 2
  case _ => -1
}

def main(args: Array[String]) {
  println(func1("one"))
  println(func2("one"))
  //若是偏函數當中沒有匹配上,那麼就會報錯,咱們能夠經過isDefinedAt來進行判斷
 // println(func1("three"))
  println
(func1.isDefinedAt("three"))
  if(func1.isDefinedAt("three")){
    println("hello world")
  }else{
    println("world hello")
  }
}

 

 

 

 

1三、scala當中的類型參數(瞭解)

類型參數主要就是研究scala當中的類或者scala當中的方法的泛型

一、 scala當中的類的泛型

 

object Demo8 {
  def main(args: Array[String]): Unit = {
    val result1 = new MyClass("hello",50)
    val result2 = new MyClass[Any,Any]("zhangsan","Lisi");
  }
}

/**
  *
定義一個class類,接收兩個參數,可是兩個參數都是泛型,泛型的類型,會根據咱們
  *
建立類的實例化對象的時候,動態的傳遞進行動態的推斷
  *
@param first
 
* @param second
 
* @tparam T
 
* @tparam B
 
*/
class  MyClass[T,B](first:T,second:B){
  println(first+","+second)
}

 

二、函數的泛型

咱們的函數或者方法,也能夠有類型參數

object methodType{
  def getMiddle[T](canshu:T) ={
    canshu
  }
  def main(args: Array[String]): Unit = {
    // 從參數類型來推斷類型
    println
(getMiddle(Array("Bob", "had", "a", "little", "brother")).getClass.getTypeName)
    //指定類型,並保存爲具體的函數。
   
val f = getMiddle[String] _
    println(f("Bob"))
  }
}

 

三、scala當中的上下界之泛型類型的限定

在scala當中,咱們能夠經過上界或者下界來限定咱們泛型的類型,相似於java當中的

?  extends  T   ?號就表示咱們使用的泛型,必須是T類型的子類,這種狀況叫作上界

?  super    T   ?號就表示咱們使用的泛型,必須是T類型的父類,這種狀況叫作下界

 

在scala當中上界的表示方法使用的是  「<:」, 這個符號就是表示上界,這種形式稱之爲泛型的上界。

 

在scala當中下界的表示方式使用的是  「>:」,   這個符號就是表示下界,這種形式稱之爲泛型的下界

一、泛型的上界限定

咱們能夠經過上界的限定,限定咱們傳入的類型必須是某個類型的子類

class Pair1[T <: Comparable[T]](val first: T, val second: T) {
  def smaller = if (first.compareTo(second) < 0) first else second
}

object Main1 extends App{
  override def main(args: Array[String]): Unit = {
    val p = new Pair1("hello", "Brooks")
    println(p.smaller)
  }
}

 

二、泛型的下界限定

咱們能夠經過下界的限定,限定咱們傳入的類型必須是某個類型的父類

 

class Pair2[T](val first: T, val second: T) {
  def replaceFirst[R >: T](newFirst: R) = new Pair2[R](newFirst, second)
  override def toString = "(" + first + "," + second + ")"
}

object Main2 extends App{
  override def main(args: Array[String]): Unit = {
    val p = new Pair2("Nick", "Alice")
    println(p)
    println(p.replaceFirst("Joke"))
    println(p)
  }
}

 

在Java中,T同時是A和B的子類型,稱之爲多界,形式如:<T extends A & B>。

在Scala中,對上界和下界不能有多個,可是可使用混合類型,如:[T <: A with B]。

在Java中,不支持下界的多界形式。如:<T super A & B>這是不支持的。

在Scala中,對複合類型依然可使用下界,如:[T >: A with B]。

 

 

四、scala當中的視圖界定

說白了就是將咱們的泛型轉化成了具體的類型

在Scala中,若是你想標記某一個泛型能夠隱式的轉換爲另外一個泛型,可使用:[T <% Comparable[T]],因爲Scala的Int類型沒有實現Comparable接口,因此咱們須要將Int類型隱式的轉換爲RichInt類型,好比:

咱們若是須要比較兩個值的大小,那麼咱們的兩個值必須是Comparable的子類,那麼咱們可使用泛型   T  <%  Comparable  來限制咱們泛型必須是Comparable的子類,而且咱們的泛型在執行真正比較的方法的時候,會根據咱們傳入的類型,自動推斷,進行隱式的轉換,例如咱們傳入4,2 進行比較,那麼咱們會將4, 2  這兩個類型作自動推斷,轉換成真正的RichInt類型而後再繼續進行比較

 

/**
  *
使用 <%  來實現咱們類型的隱式轉換
  *
@param first
 
* @param second
 
* @tparam T
 
*/
class Pair3[T <% Comparable[T]](val first: T, val second: T) {
  def smaller = if (first.compareTo(second) < 0) first else second
  override def toString = "(" + first + "," + second + ")"
}

object Main3 extends App {
  val p = new Pair3(4, 2)
  println(p.smaller)
}

 

 

 

五、scala當中的協變,逆變和非變

協變和逆變主要是用來解決參數化類型的泛化問題。Scala的協變與逆變是很是有特點的,徹底解決了Java中泛型的一大缺憾;舉例來講,Java中,若是有 A是 B的子類,但 Card[A] 卻不是 Card[B] 的子類;而 Scala 中,只要靈活使用協變與逆變,就能夠解決此類 Java 泛型問題;

因爲參數化類型的參數(參數類型)是可變的,當兩個參數化類型的參數是繼承關係(可泛化),那被參數化的類型是否也能夠泛化呢?Java中這種狀況下是不可泛化的,然而Scala提供了三個選擇,即協變(「+」)、逆變(「-」)和非變。

下面說一下三種狀況的含義,首先假設有參數化特徵Queue,那它能夠有以下三種定義。

(1)  trait Queue[T] {}

這是非變狀況。這種狀況下,當類型B是類型A的子類型,則Queue[B]與Queue[A]沒有任何從屬關係,這種狀況是和Java同樣的。

(2)  trait Queue[+T] {} 
         這是協變狀況。這種狀況下,當類型B是類型A的子類型,則Queue[B]也能夠認爲是Queue[A]的子類型,即Queue[B]能夠泛化爲Queue[A]。也就是被參數化類型的泛化方向與參數類型的方向是一致的,因此稱爲協變。

(3)   trait Queue[-T] {} 

這是逆變狀況。這種狀況下,當類型B是類型A的子類型,則Queue[A]反過來能夠認爲是Queue[B]的子類型。也就是被參數化類型的泛化方向與參數類型的方向是相反的,因此稱爲逆變。 

 協變、逆變、非變總結

  • C[+T]:若是A是B的子類,那麼C[A]是C[B]的子類。
  • C[-T]:若是A是B的子類,那麼C[B]是C[A]的子類。
  • C[T]: 不管A和B是什麼關係,C[A]和C[B]沒有從屬關係。

 

    

class Anmial

class Dog extends Anmial

協變

   List[Dog] extends List[Anmial]

逆變

   List[Anmial]  extends List[Dog]

不變 

   List[Dog]  =  List[Dog]

 

 

 

 

 案例

package cn.itcast.scala.enhance.covariance

class Super
class Sub extends Super
//協變
class Temp1[+A](title: String)
//逆變
class Temp2[-A](title: String)
//非變
class Temp3[A](title: String)

object Covariance_demo{
  def main(args: Array[String]) {
    //支持協變 Temp1[Sub]仍是Temp1[Super]的子類
    val t1: Temp1[Super] = new Temp1[Sub]("hello scala!!!")
    //支持逆變 Temp1[Super]是Temp1[Sub]的子類
    val t2: Temp2[Sub] = new Temp2[Super]("hello scala!!!")
    //支持非變 Temp3[Super]與Temp3[Sub]沒有從屬關係,以下代碼會報錯
    //val t3: Temp3[Sub] = new Temp3[Super]("hello scala!!!")
//val t4: Temp3[Super] = new Temp3[Sub]("hello scala!!!")
    println(t1.toString)
    println(t2.toString)
  }
}

 

 

 

 

 

1四、scala當中的Actor併發編程

一、課程目標

 目標一:熟悉Scala Actor併發編程

 目標二:爲學習Akka作準備

注:Scala Actor是scala 2.10.x版本及之前版本的Actor。

Scala在2.11.x版本中將Akka加入其中,做爲其默認的Actor,老版本的Actor已經廢棄。

 

 

 

 

二、scala當中的Actor介紹

①Scala中的併發編程思想與Java中的併發編程思想徹底不同,Scala中的Actor是一種不共享數據,依賴於消息傳遞的一種併發編程模式, 避免了死鎖、資源爭奪等狀況。在具體實現的過程當中,Scala中的Actor會不斷的循環本身的郵箱,並經過receive偏函數進行消息的模式匹配並進行相應的處理。 

②若是Actor A和 Actor B要相互溝通的話,首先A要給B傳遞一個消息,B會有一個收件箱,而後B會不斷的循環本身的收件箱, 若看見A發過來的消息,B就會解析A的消息並執行,處理完以後就有可能將處理的結果經過郵件的方式發送給A

 

 

 

 

 

三、什麼是Scala  Actor

 概念

Scala中的Actor可以實現並行編程的強大功能,它是基於事件模型的併發機制,Scala是運用消息的發送、接收來實現高併發的。

Actor能夠看做是一個個獨立的實體,他們之間是毫無關聯的。可是,他們能夠經過消息來通訊。一個Actor收到其餘Actor的信息後,它能夠根據須要做出各類相應。消息的類型能夠是任意的,消息的內容也能夠是任意的。

 

 

 java併發編程與Scala Actor編程的區別

 

對於Java,咱們都知道它的多線程實現須要對共享資源(變量、對象等)使用synchronized 關鍵字進行代碼塊同步、對象鎖互斥等等。並且,經常一大塊的try…catch語句塊中加上wait方法、notify方法、notifyAll方法是讓人很頭疼的。緣由就在於Java中多數使用的是可變狀態的對象資源,對這些資源進行共享來實現多線程編程的話,控制好資源競爭與防止對象狀態被意外修改是很是重要的,而對象狀態的不變性也是較難以保證的。

Java的基於共享數據和鎖的線程模型不一樣,Scala的actor包則提供了另一種不共享任何數據、依賴消息傳遞的模型,從而進行併發編程。

 

 Actor的執行順序

一、首先調用start()方法啓動Actor

二、調用start()方法後其act()方法會被執行

三、向Actor發送消息

四、act方法執行完成以後,程序會調用exit方法

 

 

 發送消息的方式

!

發送異步消息,沒有返回值。

!?

發送同步消息,等待返回值。

!!

發送異步消息,返回值是 Future[Any]。

注意:Future 表示一個異步操做的結果狀態,可能尚未實際完成的異步任務的結果。

        Any  是全部類的超類,Future[Any]的泛型是異步操做結果的類型。

四、Actor實戰

注意:若是要開發scala的actor的代碼,那麼須要咱們將scala的SDK添加到咱們的項目當中去

 

 第一個例子

怎麼實現actor併發編程:

一、定義一個class或者是object繼承Actor特質,注意導包import scala.actors.Actor

二、重寫對應的act方法

三、調用Actor的start方法執行Actor

四、當act方法執行完成,整個程序運行結束

import scala.actors.Actor

class Actor1  extends Actor{
  override def act(): Unit = {
    for(i <- 1 to 10){
      println("actor1====="+i)
    }
  }
}
object  Actor2 extends Actor{
  override def act(): Unit = {
    for(j <- 1 to 10){
      println("actor2====="+j)
    }
  }
}
object  Actor1{

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


}

說明:上面分別調用了兩個單例對象的start()方法,他們的act()方法會被執行,相同與在java中開啓了兩個線程,線程的run()方法會被執行

注意:這兩個Actor是並行執行的,act()方法中的for循環執行完成後actor程序就退出

 第二個例子

怎麼實現actor發送、接受消息

一、定義一個class或者是object繼承Actor特質,注意導包import scala.actors.Actor

二、重寫對應的act方法

三、調用Actor的start方法執行Actor

四、經過不一樣發送消息的方式對actor發送消息

五、act方法中經過receive方法接受消息並進行相應的處理

六、act方法執行完成以後,程序退出

 

import scala.actors.Actor
class MyActor2  extends  Actor{
  override def act(): Unit = {
    receive{
      case  "start" => println("starting......")
    //  case _ => println("我沒有匹配到任何消息")
   
}
  }
}
object MyActor2{
  def main(args: Array[String]): Unit = {
    val actor = new MyActor2
    actor.start()
    actor ! "start"
 
}
}

 

 

 第三個例子

怎麼實現actor能夠不斷地接受消息:

在act方法中可使用while(true)的方式,不斷的接受消息。

class MyActor3 extends  Actor{
  override def act(): Unit = {
    while (true){
      receive{
        case  "start" => println("starting")
        case "stop" =>println("stopping")
      }
    }
  }
}
object MyActor3{
  def main(args: Array[String]): Unit = {
    val actor = new MyActor3
    actor.start()
    actor ! "start"
   
actor ! "stop"
 
}
}

 

 

說明:在act()方法中加入了while (true) 循環,就能夠不停的接收消息

注意:發送start消息和stop的消息是異步的,可是Actor接收到消息執行的過程是同步的按順序執行

 

 第四個例子

使用react方法代替receive方法去接受消息

好處:react方式會複用線程,避免頻繁的線程建立、銷燬和切換。比receive更高效

注意:  react 若是要反覆執行消息處理,react外層要用loop,不能用while

 

class MyActor4 extends Actor{
  override def act(): Unit = {
    loop{
      react{
        case "start" => println("starting")
        case "stop" => println("stopping")
      }
    }
  }
}


object MyActor4{
  def main(args: Array[String]): Unit = {
    val actor = new MyActor4
    actor.start()
    actor ! "start"
   
actor ! "stop"

 
}
}

 

 

 第五個例子

結合case class樣例類發送消息和接受消息

 

一、將消息封裝在一個樣例類中

二、經過匹配不一樣的樣例類去執行不一樣的操做

三、Actor能夠返回消息給發送方。經過sender方法向當前消息發送方返回消息

 

 

case class AsyncMessage(id:Int,message:String)
case class SyncMessage(id:Int,message:String)
case class ReplyMessage(id:Int,message:String)
class MyActor5 extends Actor{
  override def act(): Unit = {
    loop{
      react{
        case AsyncMessage(id,message) => {
          println(s"$id,$message")
          sender ! ReplyMessage(2,"異步有返回值的消息處理成功")
        }
        case SyncMessage(id,message) =>{
          println(s"$id,$message")
          sender ! ReplyMessage(id,"我是同步消息的返回值,等到我返回以後才能繼續下一步的處理")
        }
      }
    }
  }
}

object  MyActor5{

  def main(args: Array[String]): Unit = {
    val actor: MyActor5 = new MyActor5
    actor.start()
    actor ! AsyncMessage(1,"helloworld")
    val asyncMessage: Future[Any] = actor !! AsyncMessage(2,"actorSend")
    val apply: Any = asyncMessage.apply()
    println(apply)
    println("helloworld22222")
    //同步阻塞消息
   
val syncMessage: Any = actor !? SyncMessage(3,"我是同步阻塞消息")
    println(syncMessage)
  }
}

 

 

 練習實戰

需求:

用actor併發編程寫一個單機版的WordCount,將多個文件做爲輸入,計算完成後將多個任務彙總,獲得最終的結果。

 

大體的思想步驟:

一、經過loop +react 方式去不斷的接受消息

二、利用case class樣例類去匹配對應的操做

三、其中scala中提供了文件讀取的接口Source,經過調用其fromFile方法去獲取文件內容

四、將每一個文件的單詞數量進行局部彙總,存放在一個ListBuffer中

五、最後將ListBuffer中的結果進行全局彙總。

 

import scala.actors.{Actor, Future}
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.io.{BufferedSource, Source}

case class FileName(path: String)
case class ResultTask(mapWithWord: Map[String, Int])

class WordCount extends Actor {
  override def act(): Unit = {
    loop {
      react {
        //使用loop + react的方式接受咱們的數據
       
case FileName(path: String) => {
          //使用Source來讀取文件內容
         
val file: BufferedSource = Source.fromFile(path)
          //獲取文件全部內容
         
val fileContent: String = file.mkString
          //   println(fileContent)
          //
對文件內容進行切分
         
val split: Array[String] = fileContent.split("\r\n")
          //   println(split.toBuffer)
          //
對每一行進行按照空格進行切分
          //  val map: Array[Array[String]] = split.map(x => x.split(" "))
          //
切分以後,將數據進行壓平
          //  val flatten: Array[String] = map.flatten
         
val flatten: Array[String] = split.flatMap(x => x.split(" "))
          val map1: Array[(String, Int)] = flatten.map(x => (x, 1))
          //     println(map1.toBuffer)
         
val byKey: Map[String, Array[(String, Int)]] = map1.groupBy(x => x._1)
          val values: Map[String, Int] = byKey.mapValues(x => x.length)
          sender ! ResultTask(values)
        }
      }
    }
  }
}

object WordCount {
  def main(args: Array[String]): Unit = {
    //申明一個變量,存放咱們的結果數據
   
val resultTasks = new ListBuffer[ResultTask]
    //申明一個set集合用於存放咱們異步發送的返回消息值
   
val futureSet: mutable.HashSet[Future[Any]] = new mutable.HashSet[Future[Any]]()
    //定義咱們須要統計的數據文件路徑
   
val files: Array[String] = Array("F:\\scala與spark課件資料教案\\2、scala次日\\wordCount\\1.txt", "F:\\scala與spark課件資料教案\\2、scala次日\\wordCount\\2.txt", "F:\\scala與spark課件資料教案\\2、scala次日\\wordCount\\3.txt")
    //循環遍歷咱們的數據文件,而後進行發送
   
for (f <- files) {
      val count: WordCount = new WordCount
      count.start();
      val value: Future[Any] = count !! FileName(f)
      futureSet.add(value)
    }
    while (futureSet.size > 0) {
      //過濾咱們的set集合,只取那些有值的set集合
     
val completeFuture: mutable.HashSet[Future[Any]] = futureSet.filter(x => x.isSet)
      for (future <- completeFuture) {
        // 調用apply方法,獲取到咱們的future實例,實際上就是ResultTask
       
val futureApply: Any = future.apply()
        //判斷咱們的結果值若是是ResultTask類型的話,那麼咱們就添加到咱們的ListBuffer當中去,表示已經獲取到了返回結果
       
resultTasks += futureApply.asInstanceOf[ResultTask]
        //添加完ListBuffer以後,將set集合當中的元素減小,以便於退出while循環
       
futureSet -= future
      }
    }
    println(resultTasks)
    val flatten: ListBuffer[(String, Int)] = resultTasks.map(x => x.mapWithWord).flatten
    val by: Map[String, ListBuffer[(String, Int)]] = flatten.groupBy( x => x._1)
    println(by)
    //第一個下劃線表示咱們累加以後的結果
    //
第二個下劃線表示咱們集合當中每個元組
    // _2 
表示元組當中第二個元素
   
val values: Map[String, Int] = by.mapValues(x => x.foldLeft(0)( _ + _._2))
    for((k,v) <-  values){
      println(k+"====>"+v)
    }
  }
}

 

 

 

 

1五、scala當中的文件操做和網絡請求

一、讀取文件當中每一行的數據

 

def main(args: Array[String]): Unit = {
  //注意文件的編碼格式,若是編碼格式不對,那麼讀取報錯
 
val file: BufferedSource = Source.fromFile("F:\\scala與spark課件資料教案\\3、scala第三天\\files\\file.txt","GBK");
  val lines: Iterator[String] = file.getLines()
 for(line <- lines){
   println(line)
 }
  //注意關閉文件
 
file.close()
}

 

若是要將文件內容轉數組,直接調用toArray便可

 

二、讀取詞法單元和數字

若是想將以某個字符或某個正則表達式分開的字符成組讀取,能夠這麼作:

def main(args: Array[String]): Unit = {
  val file: BufferedSource = Source.fromFile("F:\\scala與spark課件資料教案\\3、scala第三天\\files\\file2.txt","GBK");
  val split: Array[String] = file.mkString.split(" ")
  println(split.mkString("\t"))
  file.close()
}

 

三、讀取網絡資源、文件寫入、控制檯操做

一、讀取網絡資源

def main(args: Array[String]): Unit = {
  val source: BufferedSource = Source.fromURL("http://www.baidu.com")
  val string: String = source.mkString

  println(string)
  source.close()
}

 

 

 

 

二、文件寫入操做

def main(args: Array[String]): Unit = {
  val writer = new PrintWriter("F:\\scala與spark課件資料教案\\3、scala第三天\\files\\printWriter.txt")
  for(i <- 1 to 100){
    writer.println(i)
    writer.flush()
  }
  writer.close()
}
 

 

三、控制檯交互操做

def main(args: Array[String]): Unit = {
  //控制檯交互--老API
  print
("請輸入內容:")
  val consoleLine1 = Console.readLine()
  println("剛纔輸入的內容是:" + consoleLine1)

  //控制檯交互--新API
  print
("請輸入內容(新API):")
  val consoleLine2 = StdIn.readLine()
  println("剛纔輸入的內容是:" + consoleLine2)
}

 

四、scala當中的序列化

@SerialVersionUID(1L)
class Person extends Serializable{
  override def toString = name + "," + age

 
val name = "Nick"
  val
age = 20

}

object PersonMain extends App{
  override def main(args: Array[String]): Unit = {

    import java.io.{FileOutputStream, FileInputStream, ObjectOutputStream, ObjectInputStream}
    val nick = new Person
    val out = new ObjectOutputStream(new FileOutputStream("Nick.obj"))
    out.writeObject(nick)
    out.close()

    val in = new ObjectInputStream(new FileInputStream("Nick.obj"))
    val saveNick = in.readObject()
    in.close()
    println(saveNick)
  }
}

 

五、scala當中的正則表達式

咱們能夠經過正則表達式匹配一個句子中全部符合匹配的內容,並輸出:

 

def main(args: Array[String]): Unit = {
  import scala.util.matching.Regex
  val pattern1 = new Regex("(S|s)cala")
  val pattern2 = "(S|s)cala".r
  val str = "Scala is scalable and cool"
 
println((pattern2 findAllIn str).mkString(","))

}

1六、隱式轉換和隱式參數

 

  隱式轉換

Scala提供的隱式轉換和隱式參數功能,是很是有特點的功能。是Java等編程語言所沒有的功能。它能夠容許你手動指定,將某種類型的對象轉換成其餘類型的對象或者是給一個類增長方法。經過這些功能,能夠實現很是強大、特殊的功能。

Scala的隱式轉換,其實最核心的就是定義隱式轉換方法,即implicit conversion function。定義的隱式轉換方法,只要在編寫的程序內引入,就會被Scala自動使用。Scala會根據隱式轉換方法的簽名,在程序中使用到隱式轉換方法接收的參數類型定義的對象時,會自動將其傳入隱式轉換方法,轉換爲另一種類型的對象並返回。這就是「隱式轉換」。其中全部的隱式值和隱式方法必須放到object中。

然而使用Scala的隱式轉換是有必定的限制的,總結以下:

  • implicit關鍵字只能用來修飾方法、變量(參數)。
  • 隱式轉換的方法在當前範圍內纔有效。若是隱式轉換不在當前範圍內定義(好比定義在另外一個類中或包含在某個對象中),那麼必須經過import語句將其導。

  隱式參數

所謂的隱式參數,指的是在函數或者方法中,定義一個用implicit修飾的參數,此時Scala會嘗試找到一個指定類型的,用implicit修飾的參數,即隱式值,並注入參數。

Scala會在兩個範圍內查找:

  • 當前做用域內可見的val或var定義的隱式變量;
  • 一種是隱式參數類型的伴生對象內的隱式值;

 

 

  隱式轉換方法做用域與導入

(1)Scala默認會使用兩種隱式轉換,一種是源類型或者目標類型的伴生對象內的隱式轉換方法;一種是當前程序做用域內的能夠用惟一標識符表示的隱式轉換方法。

(2)若是隱式轉換方法不在上述兩種狀況下的話,那麼就必須手動使用import語法引入某個包下的隱式轉換方法,好比import test._。一般建議,僅僅在須要進行隱式轉換的地方,用import導入隱式轉換方法,這樣能夠縮小隱式轉換方法的做用域,避免不須要的隱式轉換。

  隱式轉換的時機

(1)當對象調用類中不存在的方法或成員時,編譯器會自動將對象進行隱式轉換

(2)當方法中的參數的類型與目標類型不一致時

 

  隱式轉換和隱式參數案例

隱式轉換案例一(將咱們的Double類型的數據自動轉換成Int類型)

object Chapter14 {
  implicit def ConvertDoubleToInt(first:Double):Int= first.toInt
}

object Convert{
  //導入隱式轉換的方法
 
import Chapter14._
  def main(args: Array[String]): Unit = {
    val first:Int = 3.5
  }
}

 

例如咱們也能夠定義貓和狗,而且讓狗學會抓老鼠的技能

object CatAndDog {
  implicit  def dogCatchMouse(dog:Dog) = new Cat()
  def main(args: Array[String]): Unit = {
      val dog = new Dog
      dog.catMouse("大黃狗")
  }
}
class Cat{
  def catMouse(name:String): Unit ={
    println(name+"catch a mouse")
  }
}
class Dog{
  def wangwangwang(name:String) ={
    println(name+"看門汪汪汪")
  }
}

 

 

隱式轉換案例二(讓File類具有RichFile類中的read方法)

import java.io.File
import scala.io.Source
object MyPredef{
  //定義隱式轉換方法
 
implicit def file2RichFile(file: File)=new RichFile(file)
}
class RichFile(val f:File) {
  def read()=Source.fromFile(f,"GBK").mkString
}
object RichFile{
  def main(args: Array[String]) {
    val f=new File("F:\\scala與spark課件資料教案\\3、scala第三天\\files\\file.txt")
    //使用import導入隱式轉換方法
   
import MyPredef._
    //經過隱式轉換,讓File類具有了RichFile類中的方法
   
val content=f.read()
    println(content)
  }
}

 

隱式轉換案例三(超人變身)

 

class Man(val name:String)
class SuperMan(val name: String) {
  def heat=print("超人打怪獸")
}
object SuperMan{
  //隱式轉換方法
 
implicit def man2SuperMan(man:Man)=new SuperMan(man.name)
  def main(args: Array[String]) {
    val hero=new Man("hero")
    //Man具有了SuperMan的方法
   
hero.heat
  }
}

 

 

隱式轉換案例四(一個類隱式轉換成具備相同方法的多個類)

 

class A(c:C) {
  def readBook(): Unit ={
    println("A說:好書好書...")
  }
}
class B(c:C){
  def readBook(): Unit ={
    println("B說:看不懂...")
  }
  def writeBook(): Unit ={
    println("B說:不會寫...")
  }
}
class C
object AB{
  //建立一個類的2個類的隱式轉換
 
implicit def C2A(c:C)=new A(c)
  implicit def C2B(c:C)=new B(c)
}
object B{
  def main(args: Array[String]) {
    //導包
    //1. import AB._
會將AB類下的全部隱式轉換導進來
    //2. import AB._C2A
只導入C類到A類的的隱式轉換方法
    //3. import AB._C2B
只導入C類到B類的的隱式轉換方法
   
import AB._
    val c=new C
    //因爲A類與B類中都有readBook(),只能導入其中一個,不然調用共同方法時代碼報錯
    //c.readBook()
    //C
類能夠執行B類中的writeBook()
   
c.writeBook()
  }
}

 

 

隱式參數案例五(員工領取薪水)

object Company{
  //在object中定義隱式值    注意:同一類型的隱式值只容許出現一次,不然會報錯
 
implicit  val aaa="zhangsan"
  implicit  val
bbb=10000.00
}
class Boss {
  //注意參數匹配的類型   它須要的是String類型的隱式值
 
def callName()(implicit name:String):String={
    name+" is coming !"
 
}
  //定義一個用implicit修飾的參數
  //
注意參數匹配的類型    它須要的是Double類型的隱式值
 
def getMoney()(implicit money:Double):String={
    " 當月薪水:"+money
  }
}
object Boss extends App{
  //使用import導入定義好的隱式值,注意:必須先加載不然會報錯
 
import Company._
  val boss =new Boss
  println(boss.callName()+boss.getMoney())

}

 

 

 

 

 

 

 

 

 

 

 

 

 

1七、scala編程實戰

 

使用Akka實現一個簡易版的spark通訊框架

 

 

 

 

 

項目概述

 需求

目前大多數的分佈式架構底層通訊都是經過RPC實現的,RPC框架很是多,好比前咱們學過的Hadoop項目的RPC通訊框架,可是Hadoop在設計之初就是爲了運行長達數小時的批量而設計的,在某些極端的狀況下,任務提交的延遲很高,因此Hadoop的RPC顯得有些笨重。

Spark 的RPC是經過Akka類庫實現的,Akka用Scala語言開發,基於Actor併發模型實現,Akka具備高可靠、高性能、可擴展等特色,使用Akka能夠輕鬆實現分佈式RPC功能。

 Akka簡介

Akka基於Actor模型,提供了一個用於構建可擴展的(Scalable)、彈性的(Resilient)、快速響應的(Responsive)應用程序的平臺。

Actor模型:在計算機科學領域,Actor模型是一個並行計算(Concurrent Computation)模型,它把actor做爲並行計算的基本元素來對待:爲響應一個接收到的消息,一個actor可以本身作出一些決策,如建立更多的actor,或發送更多的消息,或者肯定如何去響應接收到的下一個消息。

 

Actor是Akka中最核心的概念,它是一個封裝了狀態和行爲的對象,Actor之間能夠經過交換消息的方式進行通訊,每一個Actor都有本身的收件箱(Mailbox)。經過Actor可以簡化鎖及線程管理,能夠很是容易地開發出正確地併發程序和並行系統,Actor具備以下特性:

 

(1)、提供了一種高級抽象,可以簡化在併發(Concurrency)/並行(Parallelism)應用場景下的編程開發

(2)、提供了異步非阻塞的、高性能的事件驅動編程模型

(3)、超級輕量級事件處理(每GB堆內存幾百萬Actor)

 

 項目實現

 實戰一:

利用Akkaactor編程模型,實現2個進程間的通訊。

 架構圖

 

 重要類介紹

ActorSystem在Akka中,ActorSystem是一個重量級的結構,他須要分配多個線程,因此在實際應用中,ActorSystem一般是一個單例對象,咱們可使用這個ActorSystem建立不少Actor。

注意:

(1)、ActorSystem是一個進程中的老大,它負責建立和監督actor

(2)、ActorSystem是一個單例對象

(3)、actor負責通訊

 

 Actor

在Akka中,Actor負責通訊,在Actor中有一些重要的生命週期方法。

(1)preStart()方法:該方法在Actor對象構造方法執行後執行,整個Actor生命週期中僅執行一次。

(2)receive()方法:該方法在Actor的preStart方法執行完成後執行,用於接收消息,會被反覆執行。

 具體代碼

第一步:建立maven工程,導入jar包

<properties>

        <maven.compiler.source>1.8</maven.compiler.source>

        <maven.compiler.target>1.8</maven.compiler.target>

        <encoding>UTF-8</encoding>

        <scala.version>2.11.8</scala.version>

        <scala.compat.version>2.11</scala.compat.version>

    </properties>

 

    <dependencies>

        <dependency>

            <groupId>org.scala-lang</groupId>

            <artifactId>scala-library</artifactId>

            <version>${scala.version}</version>

        </dependency>

 

        <dependency>

            <groupId>com.typesafe.akka</groupId>

            <artifactId>akka-actor_2.11</artifactId>

            <version>2.3.14</version>

        </dependency>

 

        <dependency>

            <groupId>com.typesafe.akka</groupId>

            <artifactId>akka-remote_2.11</artifactId>

            <version>2.3.14</version>

        </dependency>

 

    </dependencies>

 

    <build>

        <sourceDirectory>src/main/scala</sourceDirectory>

        <testSourceDirectory>src/test/scala</testSourceDirectory>

        <plugins>

            <!-- 限制jdk的編譯版本插件 -->

            <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-compiler-plugin</artifactId>

                <version>3.0</version>

                <configuration>

                    <source>1.8</source>

                    <target>1.8</target>

                    <encoding>UTF-8</encoding>

                </configuration>

            </plugin>

 

 

            <plugin>

                <groupId>net.alchim31.maven</groupId>

                <artifactId>scala-maven-plugin</artifactId>

                <version>3.2.2</version>

                <executions>

                    <execution>

                        <goals>

                            <goal>compile</goal>

                            <goal>testCompile</goal>

                        </goals>

                        <configuration>

                            <args>

                                <arg>-dependencyfile</arg>

                                <arg>${project.build.directory}/.scala_dependencies</arg>

                            </args>

                        </configuration>

                    </execution>

                </executions>

            </plugin>

 

            <plugin>

                <groupId>org.apache.maven.plugins</groupId>

                <artifactId>maven-shade-plugin</artifactId>

                <version>2.4.3</version>

                <executions>

                    <execution>

                        <phase>package</phase>

                        <goals>

                            <goal>shade</goal>

                        </goals>

                        <configuration>

                            <filters>

                                <filter>

                                    <artifact>*:*</artifact>

                                    <excludes>

                                        <exclude>META-INF/*.SF</exclude>

                                        <exclude>META-INF/*.DSA</exclude>

                                        <exclude>META-INF/*.RSA</exclude>

                                    </excludes>

                                </filter>

                            </filters>

                            <transformers>

                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">

                                    <resource>reference.conf</resource>

                                </transformer>

                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">

                                    <mainClass></mainClass>

                                </transformer>

                            </transformers>

                        </configuration>

                    </execution>

                </executions>

            </plugin>

        </plugins>

    </build>

第二步:master進程代碼開發

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.ConfigFactory

//todo:利用akka的actor模型實現2個進程間的通訊-----Master

class
Master  extends Actor{
  //構造代碼塊先被執行
  println
("master constructor invoked")

  //prestart方法會在構造代碼塊執行後被調用,而且只被調用一次
 
override def preStart(): Unit = {
    println("preStart method invoked")
  }

  //receive方法會在prestart方法執行後被調用,表示不斷的接受消息
 
override def receive: Receive = {
    case "connect" =>{
      println("a client connected")
      //master發送註冊成功信息給worker
     
sender ! "success"
   
}
  }
}
object Master{
  def main(args: Array[String]): Unit = {
    val host = args(0)

    val port = args(1)

   // val host="localhost"

   // val port = 8888

    //準備配置文件信息
   
val configStr=
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$
host"
         |akka.remote.netty.tcp.port = "$
port"
      """
.stripMargin

    //配置config對象 利用ConfigFactory解析配置文件,獲取配置信息
   
val config=ConfigFactory.parseString(configStr)

    // 1、建立ActorSystem,它是整個進程中老大,它負責建立和監督actor,它是單例對象
   
val masterActorSystem = ActorSystem("masterActorSystem",config)
    // 2、經過ActorSystem來建立master actor
   
val masterActor: ActorRef = masterActorSystem.actorOf(Props(new Master),"masterActor")
    // 3、向master actor發送消息
   
masterActor ! "connect"
 
}
}

 

注意:若是使用參數傳遞的方式來接收參數,那麼必須在idea當中配置程序運行傳遞的參數

 

 

 

第三步:worker進程代碼開發

import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}

//todo:利用akka中的actor實現2個進程間的通訊-----Worker
class
Worker  extends Actor{
  println("Worker constructor invoked")

  //prestart方法會在構造代碼塊以後被調用,而且只會被調用一次
 
override def preStart(): Unit = {
    println("preStart method invoked")
    //獲取master actor的引用
    //ActorContext
全局變量,能夠經過在已經存在的actor中,尋找目標actor
    //
調用對應actorSelection方法,
    //
方法須要一個path路徑:1、通訊協議、2、master的IP地址、3、master的端口 4、建立master actor老大 5、actor層級
   
val master: ActorSelection = context.actorSelection("akka.tcp://masterActorSystem@127.0.0.1:8888/user/masterActor")

    //向master發送消息
   
master ! "connect"

 
}

  //receive方法會在prestart方法執行後被調用,不斷的接受消息
 
override def receive: Receive = {
    case "connect" =>{
      println("a client connected")
    }
    case "success" =>{
      println("註冊成功")
    }
  }
}

object Worker{
  def main(args: Array[String]): Unit = {
    //定義worker的IP地址
   
val host=args(0)
    //定義worker的端口
   
val port=args(1)

    //準備配置文件
   
val configStr=
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$
host"
         |akka.remote.netty.tcp.port = "$
port"
      """
.stripMargin

    //經過configFactory來解析配置信息
   
val config=ConfigFactory.parseString(configStr)
    // 1、建立ActorSystem,它是整個進程中的老大,它負責建立和監督actor
   
val workerActorSystem = ActorSystem("workerActorSystem",config)
    // 2、經過actorSystem來建立 worker actor
   
val workerActor: ActorRef = workerActorSystem.actorOf(Props(new Worker),"workerActor")

    //向worker actor發送消息
   
workerActor ! "connect"
 
}
}

 

實戰二

使用Akka實現一個簡易版的spark通訊框架

需求實現邏輯

一、  啓動master和worker

二、  在worker端對應的preStart方法中拿到master的引用對象,經過這個master引用向master發送註冊信息,註冊信息包含workerId, workCores, workMemory等信息

三、  master接受worker註冊信息,保存註冊信息在一個map集合當中,key爲workerId,value爲註冊信息樣例類。master將worker註冊成功的信息反饋給worker端

四、  worker接受master反饋的註冊成功信息,定時向master發送心跳信息。發送心跳信息,證實worker還活着

五、  master接受worker心跳信息,定時檢查超時worker,並從map當中移除掉超時的worker節點信息

架構圖

 

具體代碼

① Master

 

package cn.itcast.spark

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.ConfigFactory

import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import scala.concurrent.duration._


//todo:利用akka實現簡易版的spark通訊框架-----Master端

class Master  extends Actor{
  //構造代碼塊先被執行
  println("master constructor invoked")

  //定義一個map集合,用於存放worker信息
  private val workerMap = new mutable.HashMap[String,WorkerInfo]()
  //定義一個list集合,用於存放WorkerInfo信息,方便後期按照worker上的資源進行排序
  private val workerList = new ListBuffer[WorkerInfo]
  //master定時檢查的時間間隔
  val CHECK_OUT_TIME_INTERVAL=15000 //15秒

  //prestart方法會在構造代碼塊執行後被調用,而且只被調用一次
  override def preStart(): Unit = {
    println("preStart method invoked")

      //master定時檢查超時的worker
    //須要手動導入隱式轉換
    import context.dispatcher
    context.system.scheduler.schedule(0 millis,CHECK_OUT_TIME_INTERVAL millis,self,CheckOutTime)
  }

  //receive方法會在prestart方法執行後被調用,表示不斷的接受消息
  override def receive: Receive = {
    //master接受worker的註冊信息
    case RegisterMessage(workerId,memory,cores) =>{
        //判斷當前worker是否已經註冊
      if(!workerMap.contains(workerId)){
        //保存信息到map集合中
        val workerInfo = new WorkerInfo(workerId,memory,cores)
        workerMap.put(workerId,workerInfo)
        //保存workerinfo到list集合中
        workerList +=workerInfo

        //master反饋註冊成功給worker
        sender ! RegisteredMessage(s"workerId:$workerId 註冊成功")
      }
    }
      //master接受worker的心跳信息
    case SendHeartBeat(workerId)=>{
      //判斷worker是否已經註冊,master只接受已經註冊過的worker的心跳信息
      if(workerMap.contains(workerId)){
        //獲取workerinfo信息
        val workerInfo: WorkerInfo = workerMap(workerId)
        //獲取當前系統時間
        val lastTime: Long = System.currentTimeMillis()

        workerInfo.lastHeartBeatTime=lastTime
      }
    }
    case CheckOutTime=>{
      //過濾出超時的worker 判斷邏輯: 獲取當前系統時間 - worker上一次心跳時間 >master定時檢查的時間間隔
        val outTimeWorkers: ListBuffer[WorkerInfo] = workerList.filter(x => System.currentTimeMillis() -x.lastHeartBeatTime > CHECK_OUT_TIME_INTERVAL)
      //遍歷超時的worker信息,而後移除掉超時的worker
      for(workerInfo <- outTimeWorkers){
        //獲取workerid
        val workerId: String = workerInfo.workerId
        //從map集合中移除掉超時的worker信息
        workerMap.remove(workerId)
        //從list集合中移除掉超時的workerInfo信息
        workerList -= workerInfo

        println("超時的workerId:" +workerId)
      }
      println("活着的worker總數:" + workerList.size)

      //master按照worker內存大小進行降序排列
     println(workerList.sortBy(x => x.memory).reverse.toList)
    }
  }
}
object Master{
  def main(args: Array[String]): Unit = {
    //master的ip地址
    val host=args(0)
    //master的port端口
    val port=args(1)

    //準備配置文件信息
    val configStr=
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
      """.stripMargin

    //配置config對象 利用ConfigFactory解析配置文件,獲取配置信息
    val config=ConfigFactory.parseString(configStr)

    // 一、建立ActorSystem,它是整個進程中老大,它負責建立和監督actor,它是單例對象
    val masterActorSystem = ActorSystem("masterActorSystem",config)
    // 二、經過ActorSystem來建立master actor
    val masterActor: ActorRef = masterActorSystem.actorOf(Props(new Master),"masterActor")
    // 三、向master actor發送消息
    //masterActor ! "connect"
  }
}
 

② Worker

package cn.itcast.spark

import java.util.UUID
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._


//todo:利用akka實現簡易版的spark通訊框架-----Worker端
class Worker(val memory:Int,val cores:Int,val masterHost:String,val masterPort:String)  extends Actor{
  println("Worker constructor invoked")

  //定義workerId
  private val workerId: String = UUID.randomUUID().toString

  //定義發送心跳的時間間隔
  val SEND_HEART_HEAT_INTERVAL=10000  //10秒

  //定義全局變量
  var master: ActorSelection=_

  //prestart方法會在構造代碼塊以後被調用,而且只會被調用一次
  override def preStart(): Unit = {
    println("preStart method invoked")
    //獲取master actor的引用
    //ActorContext全局變量,能夠經過在已經存在的actor中,尋找目標actor
    //調用對應actorSelection方法,
    // 方法須要一個path路徑:一、通訊協議、二、master的IP地址、三、master的端口 四、建立master actor老大 五、actor層級
     master= context.actorSelection(s"akka.tcp://masterActorSystem@$masterHost:$masterPort/user/masterActor")

    //向master發送註冊信息,將信息封裝在樣例類中,主要包含:workerId,memory,cores
    master ! RegisterMessage(workerId,memory,cores)

  }

  //receive方法會在prestart方法執行後被調用,不斷的接受消息
  override def receive: Receive = {
    //worker接受master的反饋信息
    case RegisteredMessage(message) =>{
      println(message)

      //向master按期的發送心跳
      //worker先本身給本身發送心跳
      //須要手動導入隱式轉換
      import context.dispatcher
      context.system.scheduler.schedule(0 millis,SEND_HEART_HEAT_INTERVAL millis,self,HeartBeat)
    }
      //worker接受心跳
    case HeartBeat =>{
      //這個時候纔是真正向master發送心跳
      master ! SendHeartBeat(workerId)
    }
  }
}

object Worker{
  def main(args: Array[String]): Unit = {
    //定義worker的IP地址
    val host=args(0)
    //定義worker的端口
    val port=args(1)

    //定義worker的內存
    val memory=args(2).toInt
    //定義worker的核數
    val cores=args(3).toInt
    //定義master的ip地址
    val masterHost=args(4)
    //定義master的端口
    val masterPort=args(5)

    //準備配置文件
    val configStr=
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = "$host"
         |akka.remote.netty.tcp.port = "$port"
      """.stripMargin

    //經過configFactory來解析配置信息
    val config=ConfigFactory.parseString(configStr)
    // 一、建立ActorSystem,它是整個進程中的老大,它負責建立和監督actor
    val workerActorSystem = ActorSystem("workerActorSystem",config)
    // 二、經過actorSystem來建立 worker actor
    val workerActor: ActorRef = workerActorSystem.actorOf(Props(new Worker(memory,cores,masterHost,masterPort)),"workerActor")

    //向worker actor發送消息
    workerActor ! "connect"
  }
}
 

 

 

③ WorkerInfo

package cn.itcast.spark

//封裝worker信息
class WorkerInfo(val workerId:String,val memory:Int,val cores:Int) {
        //定義一個變量用於存放worker上一次心跳時間
      var lastHeartBeatTime:Long=_

  override def toString: String = {
    s"workerId:$workerId , memory:$memory , cores:$cores"
  }
}

 

 

④ 樣例類

package cn.itcast.spark

trait RemoteMessage  extends Serializable{

}

//worker向master發送註冊信息,因爲不在同一進程中,須要實現序列化
case class RegisterMessage(val workerId:String,val memory:Int,val cores:Int) extends RemoteMessage
//master反饋註冊成功信息給worker,因爲不在同一進程中,也須要實現序列化
case class RegisteredMessage(message:String) extends RemoteMessage
//worker向worker發送心跳 因爲在同一進程中,不須要實現序列化
case object HeartBeat
//worker向master發送心跳,因爲不在同一進程中,須要實現序列化
case class SendHeartBeat(val workerId:String) extends RemoteMessage
//master本身向本身發送消息,因爲在同一進程中,不須要實現序列化
case object CheckOutTime
相關文章
相關標籤/搜索