Spark學習之scala編程


 Scala語言基礎

1Scala語言簡介

Scala是一種多范式的編程語言,其設計的初衷是要集成面向對象編程和函數式編程的各種特性。Scala運行於Java平台(Java虛擬機),並兼容現有的Java程序。

學習Scala編程語言,為后續學習Spark奠定基礎。

 

 

2、為什么要學Scala

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

速度快Scala語言表達能力強,一行代碼抵得上Java多行,開發速度快;Scala是靜態編譯的,所以和JRuby,Groovy比起來速度會快很多

l  能融合到Hadoop生態圈:Hadoop現在是大數據事實標准,Spark並不是要取代Hadoop,而是要完善Hadoop生態。JVM語言大部分可能會想到Java,但Java做出來的API太丑,或者想實現一個優雅的API太費勁。 

 

 

德國的一位資深Java專家Adam Bien在參加JavaOne 2008大會(他在會上有一個技術講座)期間,親耳聽到,有人問Java之父James Gosling“除了Java之外,你現在最希望在JVM上使用什么語言?的時候,他出人意料的回答:“Scala幾乎是脫口而出。

 

 

3、安裝JDK

因為Scala是運行在JVM平台上的,所以安裝Scala之前要安裝JDK,安裝JDK1.8版本

4、下載和安裝Scala

① Windows安裝Scala編譯器

訪問Scala官網http://www.scala-lang.org/下載Scala編譯器安裝包,目前最新版本是2.12.x,但是目前大多數的框架都是用2.11.x編寫開發的,Spark2.x使用的就是2.11.x,所以這里推薦2.11.x版本,下載scala-2.11.12.msi后點擊下一步就可以了

1) 安裝JDK

2) 下載Scalahttp://www.scala-lang.org/download/

3) 安裝Scala

設置環境變量:SCALA_HOMEPATH路徑

4) 驗證Scala

 

② Linux安裝Scala編譯器

下載Scala地址http://downloads.typesafe.com/scala/2.11.12/scala-2.11.12.tgz然后解壓Scala到指定目錄

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

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

vi /etc/profile

export JAVA_HOME=/usr/java/jdk1.8.0_111

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

 

5、Scala的運行環境

l REPLRead Evaluate Print Loop):命令行

l IDE:圖形開發工具

n The Scala IDE (Based on Eclipse)http://scala-ide.org/

n IntelliJ IDEA with Scala pluginhttp://www.jetbrains.com/idea/download/

n Netbeans IDE with the Scala plugin

6、Scala開發工具安裝IDEA

由於IDEAScala插件更優秀,大多數Scala程序員都選擇IDEA,可以到http://www.jetbrains.com/idea/download/下載社區免費版,點擊下一步安裝即可,安裝時如果有網絡可以選擇在線安裝Scala插件。這里我們使用離線安裝Scala插件:

 

安裝IDEA,點擊下一步即可。由於我們離線安裝插件,所以點擊Skip All and Set Defaul

下載IEDAscala插件,地址http://plugins.jetbrains.com/plugin/1347-scala

 

 

 

1.安裝Scala插件Configure -> Plugins -> Install plugin from disk -> 選擇Scala插件 -> OK -> 重啟IDEA

 

 

 

 

7、Scala的常用數據類型

注意:在Scala中,任何數據都是對象。例如:

 

 

 

③ 數值類型:ByteShortIntLongFloatDouble

l Byte:  8位有符號數字,從-128 127

l Short: 16位有符號數據,從-32768 32767

l Int:  32位有符號數據

l Long64位有符號數據

 

例如:

val a:Byte = 10

a+10

得到:res9: Int = 20

這里的res9是新生成變量的名字

 

val b:Short = 20

a+b

 

注意:在Scala中,定義變量可以不指定類型,因為Scala會進行類型的自動推導。

 

④ 字符類型和字符串類型:CharString

對於字符串,在Scala中可以進行插值操作。

        

 

注意:前面有個s;相當於執行:"My Name is " + s1

 

 

⑤ Unit類型:相當於Java中的void類型

 

⑥ Nothing類型:一般表示在執行過程中,產生了Exception

例如,我們定義一個函數如下:

 

8、Scala變量的申明和使用

使用valvar申明變量

例如:scala> val answer = 8 * 3 + 2

 

可以在后續表達式中使用這些名稱

l valvalue 簡寫,表示的意思為值,不可變

要申明其值可變的變量:val

l varvariable 簡寫,表示的變量,可以改變值

要申明其值不可變的變量:var

 

l 例子


object VariableTest {
  def main(args: Array[String]) {
    //使用val定義的變量值是不可變的,相當於java里用final修飾的變量
    val i = 1
    //使用var定義的變量是可變得,在Scala中鼓勵使用val
    var s = "hello"
    //Scala編譯器會自動推斷變量的類型,必要的時候可以指定類型

    //變量名在前,類型在后
    val str: String = "world"
  }
}

 

 

注意:可以不用顯式指定變量的類型,Scala會進行自動的類型推到

9、Scala的條件表達式

Scalaif/else語法結構和JavaC++一樣。

不過,在Scala中,if/else是表達式,有值,這個值就是跟在ifelse之后的表達式的值。

  • Scala的的條件表達式比較簡潔,例如:


object ConditionTest {
  def main(args: Array[String]) {
    val x = 1
    //判斷x的值,將結果賦給y
    val y = if (x > 0) 1 else -1
    //打印y的值
    println(y)

    //支持混合類型表達式
    val z = if (x > 1) 1 else "error"
    //打印z的值
    println(z)

    //如果缺失else,相當於if (x > 2) 1 else ()
    val m = if (x > 2) 1
    println(m)

    //在scala中每個表達式都有值,scala中有個Unit類,寫做(),相當於Java中的void
    val n = if (x > 2) 1 else ()
    println(n)

    //if和else if
    val k = if (x < 0) 0
    else if (x >= 1) 1 else -1
    println(k)
  }
}

 

10、塊表達式


object BlockExpressionTest {
  def main(args: Array[String]) {
    val x = 0
    //在scala中{}中課包含一系列表達式,塊中最后一個表達式的值就是塊的值
    //下面就是一個塊表達式
    val result = {
      if (x < 0){
        -1
      } else if(x >= 1) {
        1
      } else {
        "error"
      }
    }
    //result的值就是塊表達式的結果
    println(result)
  }
}

 

11、Scala的循環

Scala擁有與JavaC++相同的whiledo循環

Scala中,可以使用forforeach進行迭代

  • 使用for循環案例:

     

注意:

*<-  表示Scala中的generator,即:提取符

*)第三種寫法是第二種寫法的簡寫

 

  • for循環中,還可以使用yield關鍵字來產生一個新的集合

 

在上面的案例中,我們將list集合中的每個元素轉換成了大寫,並且使用yield關鍵字生成了一個新的集合。

 

  • 使用while循環:注意使用小括號,不是中括號

 

  • 使用do ... while循環

 

 

  • 使用foreach進行迭代

     

     

注意:在上面的例子中,foreach接收了另一個函數(println)作為值

 

12、ScalaLazy值(懶值)

val被申明為lazy時,它的初始化將被推遲,直到我們首次對它取值。

 

 

一個更為復雜一點的例子:讀取文件:

 


 

13、異常的處理

Scala異常的工作機制和Java或者C++一樣。直接使用throw關鍵字拋出異常。

 

使用try...catch...finally來捕獲和處理異常:

 

 

 

14、Scala中的數組

Scala數組的類型:

定長數組:使用關鍵字Array

 

變長數組:使用關鍵字ArrayBuffer

 

 

l 遍歷數組 

 

 

l Scala數組的常用操作

 

l Scala的多維數組

 

Java一樣,多維數組是通過數組的數組來實現的。

l 也可以創建不規則的數組,每一行的長度各不相同。

 

二、數組、映射、元組

1、 數組

① 定長數組和變長數組

import scala.collection.mutable.ArrayBuffer


object ArrayTest {

  def main(args: Array[String]) {

    //初始化一個長度為8的定長數組,其所有元素均為0
    val arr1 = new Array[Int](8)
    //直接打印定長數組,內容為數組的hashcode值
    println(arr1)
    //將數組轉換成數組緩沖,就可以看到原數組中的內容了
    //toBuffer會將數組轉換長數組緩沖
    println(arr1.toBuffer)

    //注意:如果new,相當於調用了數組的apply方法,直接為數組賦值
    //初始化一個長度為1的定長數組
    val arr2 = Array[Int](10)
    println(arr2.toBuffer)

    //定義一個長度為3的定長數組
    val arr3 = Array("hadoop", "storm", "spark")
    //使用()來訪問元素
    println(arr3(2))

    //////////////////////////////////////////////////
    //變長數組(數組緩沖)
    //如果想使用數組緩沖,需要導入import scala.collection.mutable.ArrayBuffer包
    val ab = ArrayBuffer[Int]()
    //向數組緩沖的尾部追加一個元素
    //+=尾部追加元素
    ab += 1
    //追加多個元素
    ab += (2, 3, 4, 5)
    //追加一個數組++=
    ab ++= Array(6, 7)
    //追加一個數組緩沖
    ab ++= ArrayBuffer(8,9)
    //打印數組緩沖ab

    //在數組某個位置插入元素用insert
    ab.insert(0, -1, 0)
    //刪除數組某個位置的元素用remove
    ab.remove(8, 2)
    println(ab)

  }
}

 

 

② 遍歷數組

  • 增強for循環
  • 好用的until會生成腳標,0 until 10 包含0不包含10

 

 

object ForArrayTest {

  def main(args: Array[String]) {
    //初始化一個數組
    val arr = Array(1,2,3,4,5,6,7,8)
    //增強for循環
    for(i <- arr)
      println(i)

    //好用的until會生成一個Range
    //reverse是將前面生成的Range反轉
    for(i <- (0 until arr.length).reverse)
      println(arr(i))
  }
}

 

 

③ 數組轉換

yield關鍵字將原始的數組進行轉換會產生一個新的數組,原始的數組不變

 

 


object ArrayYieldTest {
  def main(args: Array[String]) {
    //定義一個數組
    val arr = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
    //將偶數取出乘以10后再生成一個新的數組
    val res = for (e <- arr if e % 2 == 0) yield e * 10
    println(res.toBuffer)

    //更高級的寫法,用着更爽
    //filter是過濾,接收一個返回值為boolean的函數
    //map相當於將數組中的每一個元素取出來,應用傳進去的函數
    val r = arr.filter(_ % 2 == 0).map(_ * 10)
    println(r.toBuffer)

  }
}

 

④ 數組常用算法

Scala中,數組上的某些方法對數組進行相應的操作非常方便!

 

 

 

2、 映射

Scala中,把哈希表這種數據結構叫做映射

① 構建映射

 

 

② 獲取和修改映射中的值

 

 

好用的getOrElse

 

 

注意:在Scala中,有兩種Map,一個是immutable包下的Map,該Map中的內容不可變;另一個是mutable包下的Map,該Map中的內容可變

例子:

 

 

注意:通常我們在創建一個集合是會用val這個關鍵字修飾一個變量(相當於java中的final),那么就意味着該變量的引用不可變,該引用中的內容是不是可變,取決於這個引用指向的集合的類型

例子:如下這段超簡單的循環即可遍歷映射中所有的鍵/值對偶

 

 

 

3、 元組

映射是K/V對偶的集合,對偶是元組的最簡單形式,元組可以裝着多個不同類型的值。

① 創建元組

 

② 獲取元組中的值

 

③ 將對偶的集合轉換成映射

 

④ 拉鏈操作

zip命令可以將多個值綁定在一起

 

注意:如果兩個數組的元素個數不一致,拉鏈操作后生成的數組的長度為較小的那個數組的元素個數

 

三、Scala語言的函數式編程

4、 調用方法和函數

Scala中的+ - * / %等操作符的作用與Java一樣,位操作符 & | ^ >> <<也一樣。只是有

一點特別的:這些操作符實際上是方法。例如:

a + b

是如下方法調用的簡寫:

a.+(b)

a 方法 b可以寫成 a.方法(b)

5、 定義方法和函數

① 定義方法

 

方法的返回值類型可以不寫,編譯器可以自動推斷出來,但是對於遞歸方法,必須指定返回類型

② 定義函數

 

③ 方法和函數的區別

在函數式編程語言中,函數是“頭等公民”,它可以像任何其他數據類型一樣被傳遞和操作

案例:首先定義一個方法,再定義一個函數,然后將函數傳遞到方法里面

 


object MethodAndFunctionTest {
  //定義一個方法
  //方法m2參數要求是一個函數,函數的參數必須是兩個Int類型

  //返回值類型也是Int類型
  def m1(f: (Int, Int) => Int) : Int = {
    f(2, 6)
  }

  //定義一個函數f1,參數是兩個Int類型,返回值是一個Int類型
  val f1 = (x: Int, y: Int) => x + y
  //再定義一個函數f2
  val f2 = (m: Int, n: Int) => m * n

  //main方法
  def main(args: Array[String]) {

    //調用m1方法,並傳入f1函數
    val r1 = m1(f1)
    println(r1)

    //調用m1方法,並傳入f2函數
    val r2 = m1(f2)
    println(r2)
  }
}

 

 

④ 將方法轉換成函數(神奇的下划線)

 

6、 Scala的函數和方法的使用

可以使用Scala的預定義函數

例如:求兩個值的最大值

 

 

也可以使用def關鍵字自定義函數

語法:

 

示例:

 

 

7、 Scala函數的求值策略

  • Scala中,有兩種函數參數的求值策略

l Call By Value:對函數實參求值,且僅求一次

l Call By Name:函數實參每次在函數體內被用到時都會求值

  

我們來分析一下,上面兩個調用執行的過程:

一份復雜一點的例子:

  • Scala中的函數參數

l 默認參數

l 代名參數

l 可變參數

8、 Scala中的函數

Scala中,函數是“頭等公民”,就和數字一樣。可以在變量中存放函數,即:將函數作為變量的值(值函數)
 

9、 匿名函數

 

10、 帶函數參數的函數,即:高階函數

示例1

*)首先,定義一個最普通的函數

 

*)再定義一個高階函數 

 

*)分析這個高階函數調用的過程 

 

示例2

 

在這個例子中,首先定義了一個普通的函數mytest,然后定義了一個高階函數myFunctionmyFunction接收三個參數:第一個f是一個函數參數,第二個是x,第三個是y。而f是一個函數參數,本身接收兩個Int的參數,返回一個Int的值。

11、 閉包

就是函數的嵌套,即:在一個函數定義中,包含另外一個函數的定義;並且在內函數中可以訪問外函數中的變量

測試上面的函數:

 

12、 柯里化:Currying

柯里化函數(Curried Function)是把具有多個參數的函數轉換為一條函數鏈,每個節點上是單一參數。

 

一個簡單的例子:
 

13、 高階函數示例
 

示例1

 

 

示例2 

示例3 

示例4 

示例5 

在這個例子中,可以被2整除的被分到一個分區;不能被2整除的被分到另一個分區。

示例6 

示例7  

示例8 

在這個例子中,分為兩步:

(1)(1,2,3)(4,5,6)這兩個集合合並成一個集合

(2)再對每個元素乘以2

三、Scala語言的面向對象

1、 面向對象的基本概念

把數據及對數據的操作方法放在一起,作為一個相互依存的整體——對象

面向對象的三大特征:

  封裝

  繼承

  多態

2、 類的定義

簡單類和無參方法:
 

案例:注意沒有class前面沒有public關鍵字修飾。

 

如果要開發main方法,需要將main方法定義在該類的伴生對象中,即:object對象中,(后續做詳細的討論)。
 

3、 屬性的gettersetter方法

當定義屬性是private時候,scala會自動為其生成對應的getset方法

private var stuName:String = "Tom"

  • get方法: stuName    ----> s2.stuName() 由於stuName是方法的名字,所以可以加上一個括號
  • set方法: stuName_=  ----> stuName_= 是方法的名字

定義屬性:private var money:Int = 1000 希望money只有get方法,沒有set方法??

  • 辦法:將其定義為常量private val money:Int = 1000

l  private[this]的用法:該屬性只屬於該對象私有,就不會生成對應的setget方法。如果這樣,就不能直接調用,例如:s1.stuName ---> 錯誤

4、 內部類(嵌套類)

我們可以在一個類的內部在定義一個類,如下:我們在Student類中,再定義了一個Course類用於保存學生選修的課程。

 

開發一個測試程序進行測試:

5、 類的構造器

類的構造器分為:主構造器、輔助構造器

l 主構造器:和類的聲明結合在一起,只能有一個主構造器

Student4(val stuName:String,val stuAge:Int)

(1) 定義類的主構造器:兩個參數

(2) 聲明了兩個屬性:stuNamestuAge 和對應的getset方法
 

輔助構造器:可以有多個輔助構造器,通過關鍵字this來實現

 

6、 Scala中的Object對象

Scala沒有靜態的修飾符,但Object對象下的成員都是靜態的 ,若有同名的class,這其作為它的伴生類。在Object中一般可以為伴生類做一些初始化等操作。

下面是Java中的靜態塊的例子。在這個例子中,我們對JDBC進行了初始化。

Scala中的Object就相當於Java中靜態塊。

Object對象的應用

  單例對象
 

  使用應用程序對象:可以省略main方法;需要從父類App繼承。

7、 Scala中的apply方法

遇到如下形式的表達式時,apply方法就會被調用:

Object(參數1,參數2,......,參數N)

通常,這樣一個apply方法返回的是伴生類的對象;其作用是為了省略new 關鍵字

Objectapply方法舉例:

    

8、 Scala中的繼承

ScalaJava一樣,使用extends關鍵字擴展類。

案例一:Employee類繼承Person

 

l 案例二:在子類中重寫父類的方法

l 案例三:使用匿名子類
 

l 案例四:使用抽象類。抽象類中包含抽象方法,抽象類只能用來繼承。

l 案例五:使用抽象字段。抽象字段就是一個沒有初始值的字段

9、 Scala中的trait(特質)

trait就是抽象類trait跟抽象類最大的區別:trait支持多重繼承

 

10、 包的使用

Scala的包和Java中的包或者C++中的命名空間的目的是相同的:管理大型程序中的名稱。

Scala中包的定義和使用:

  包的定義
 

  包的引入:Scala中依然使用import作為引用包的關鍵字,例如

  而且Scala中的import可以寫在任意地方 

 

11、 包對象

包可以包含類、對象和特質,但不能包含函數或者變量的定義。很不幸,這是Java虛擬機的局限。

把工具函數或者常量添加到包而不是某個Utils對象,這是更加合理的做法。Scala中,包對象的出現正是為了解決這個局限。

Scala中的包對象:常量,變量,方法,類,對象,trait(特質)

12、 Scala中的文件訪問

l  讀取行

l  讀取字符

其實這里的source就指向了這個文件中的每個字符。

 

URL或其他源讀取:注意指定字符集UTF-8

讀取二進制文件:Scala中並不支持直接讀取二進制,但可以通過調用JavaInputStream來進行讀入。

寫入文本文件

四、Scala中的集合

Scala的集合有三大類:序列Seq、集Set、映射Map,所有的集合都擴展自Iterable特質

Scala中集合有可變(mutable)和不可變(immutable)兩種類型,immutable類型的集合初始化后就不能改變了(注意與val修飾的變量進行區別)

1、 可變集合和不可變集合

l 可變集合

l 不可變集合:

  集合從不改變,因此可以安全地共享其引用。

  甚至是在一個多線程的應用程序當中也沒問題。

 集合的操作:

2、 列表

不可變列表(List
 

不可變列表的相關操作:
 

可變列表(LinkedList):scala.collection.mutable
 

3、 序列

常用的序列有:VectorRange

  VectorArrayBuffer的不可變版本,是一個帶下標的序列

  Range表示一個整數序列

 

 

4、 集(Set)和集的操作

l 集Set是不重復元素的集合

和列表不同,集並不保留元素插入的順序。默認以Hash集實現

 

示例1:創建集

 

示例2 :集的操作

5、 模式匹配

Scala有一個強大的模式匹配機制,可以應用在很多場合:

  switch語句

  類型檢查

 

Scala還提供了樣本類(case class),對模式匹配進行了優化

 

模式匹配示例:

更好的switch

 

l Scala的守衛

l 模式匹配中的變量

l 類型模式

l 匹配數組和列表

6、 樣本類(CaseClass)

簡單的來說,Scalacase class就是在普通的類定義前加case這個關鍵字,然后你可以對這些類來模式匹配。

 

case class帶來的最大的好處是它們支持模式識別。

 

首先,回顧一下前面的模式匹配:

其次,如果我們想判斷一個對象是否是某個類的對象,跟Java一樣可以使用isInstanceOf

最后,在Scala中有一種更簡單的方式來判斷,就是case class
 

注意:需要在class前面使用case關鍵字。

 

 

五、Scala語言的高級特性

1、 什么是泛型類

Java或者C++一樣,類和特質可以帶類型參數。在Scala中,使用方括號來定義類型參數

測試程序:
 

2、 什么是泛型函數

函數和方法也可以帶類型參數。和泛型類一樣,我們需要把類型參數放在方法名之后。

注意:這里的ClassTag是必須的,表示運行時的一些信息,比如類型。

3、 Upper Bounds Lower Bounds

類型的上界和下界,是用來定義類型變量的范圍。它們的含義如下:

  S <: T

這是類型上界的定義。也就是S必須是類型T的子類(或本身,自己也可以認為是自己的子類。

 

  U >: T

這是類型下界的定義。也就是U必須是類型T的父類(或本身,自己也可以認為是自己的父類)

 

l 一個簡單的例子:
 

l 一個復雜一點的例子(上界):

l 再來看一個例子:

4、 視圖界定(View bounds

它比 <: 適用的范圍更廣,除了所有的子類型,還允許隱式轉換過去的類型。用 <% 表示。盡量使用視圖界定,來取代泛型的上界,因為適用的范圍更加廣泛。

示例:

上面寫過的一個列子。這里由於T的上界是String,當我們傳遞100200的時候,就會出現類型不匹配。
 

但是100200是可以轉成字符串的,所以我們可以使用視圖界定讓addTwoString方法接收更廣泛的數據類型,即:字符串及其子類、可以轉換成字符串的類型

注意:使用的是 <% 
 

l 但實際運行的時候,會出現錯誤:

 

這是因為:Scala並沒有定義如何將Int轉換成String的規則,所以要使用視圖界定,我們就必須創建轉換的規則。

l 創建轉換規則
 

l 運行成功
 

5、 協變和逆變

l 協變:

Scala的類或特征的范型定義中,如果在類型參數前面加入+符號,就可以使類或特征變為協變了。

  逆變:

在類或特征的定義中,在類型參數之前加上一個-符號,就可定義逆變范型類和特征了。
 

總結一下:Scala的協變:泛型變量的值可以是本身類型或者其子類的類型

              Scala的逆變:泛型變量的值可以是本身類型或者其父類的類型

 

6、 隱式轉換函數

所謂隱式轉換函數指的是以implicit關鍵字申明的帶有單個參數的函數。

l 前面講視圖界定時候的一個例子:

 

再舉一個例子:我們把Fruit對象轉換成了Monkey對象
 

7、 隱式參數

使用implicit申明的函數參數叫做隱式參數。我們也可以使用隱式參數實現隱式的轉換

8、 隱式類

所謂隱式類: 就是對類增加implicit 限定的類,其作用主要是對類的功能加強

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM