Scala詳細教程

Scala詳細教程

目錄

Scala詳細教程

1.Scala 介紹

1.1 什麼是 Scala Scala

 1.2 爲什麼要學 Scala

2.開發環境準備

2.1 ScalaSDK 安裝

2.1.1Window 下安裝 ScalaSDK

2.1.2Linux 下安裝 ScalaSDK

2.2 IDEA 安裝

2.3 IDEAScala 插件的離線安裝

2.4 IDEA 創建 HelloScala 工程

3.基本語法

3.1 函數式編程體驗 Spark-Shell 之 WordCount

​3.2 Scala 交互模式(cmd 窗口介紹)

3.3 數據類型

3.4 變量的定義

3.5 字符串的格式化輸出

3.6 條件表達式

3.7 循環語句和yeild 關鍵字

3.8 運算符和運算符重載

3.9 方法的定義與調用

3.10 函數的定義與調用

3.11傳值調用與傳名調用

3.12 可變參數函數

3.13 默認參數值函數

3.14 高階函數

3.15 部分參數應用函數

3.16 柯里化(Currying)

3.17 偏函數

4.集合的使用

4.1 定長數組和變長數組

4.2 map|flatten|flatMap|foreach 方法的使用

4.3 Seq 序列

4.4 Set 集

4.5  集合常用的方法

4.6並行化集合 par

4.7 Map 和 Option

4.8 案例 wordCount

5. 面向對象

5.1 scala 單例對象

5.2 scala 類

5.2.1 | 類定義 | 主構造器 | 輔助構造器

5.2.2 訪問權限

5.2.3 伴生類 | apply 方法

5.3 Trait

5.4 抽象類

5.5 繼承

5.5.1final 關鍵字

5.5.2 type 關鍵字

5.6 樣例類/樣例對象

6. 模式匹配 match case

6.1 匹配字符串/類型/守衛

6.2 匹配數組

6.3 匹配集合

6.4 匹配元組

6.5 匹配樣例類/樣例對象

7.Scala 高級語法

7.1 隱式(implicit)詳解

7.1.1 隱式參數

7.1.2 隱式的轉換類型

7.1.3 隱式類

7.2 泛型

7.3 類型約束

7.3.1 上界(UpperBounds)/下界(lowerbounds)

7.3.2 視圖界定/上下文界定


 

1.Scala 介紹

1.1 什麼是 Scala Scala

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

 1.2 爲什麼要學 Scala

  1. 優雅:這是框架設計師第一個要考慮的問題,框架的用戶是應用開發程序員,API 是否優 雅直接影響用戶體驗。
  2. 速度快:Scala 語言表達能力強,一行代碼抵得上 Java 多行,開發速度快;Scala 是靜態編 譯的,所以和 JRuby,Groovy 比起來速度會快很多。
  3. 能融合到 Hadoop 生態圈:Hadoop 現在是大數據事實標準,Spark 並不是要取代 Hadoop, 而是要完善 Hadoop 生態。JVM 語言大部分可能會想到 Java,但 Java 做出來的 API 太醜,或者想實現一個優雅的 API 太費勁。

2.開發環境準備

2.1 ScalaSDK 安裝

安裝 ScalaSDK前 ,請 確 保 已 安 裝 JDK1.8+.

2.1.1Window 下安裝 ScalaSDK

訪問 Scala 官網 https://www.scala-lang.org/download/all.html下載 Scala 編譯器安裝包,目前大多數的框架都是用 2.11.x 編寫開發的,Spark2.x 使用的就是 2.11.x,所以這裏推 薦 2.11.x 版本,下載 scala-2.11.8.msi 後點擊下一步就可以了。scala-2.11.8.zip需要自己配置環境變量

2.1.2Linux 下安裝 ScalaSDK

下載 Scala 地址https://www.scala-lang.org/download/2.12.8.html 然後解壓 Scala 到 指定目錄 tar -zxvfscala-2.11.8.tgz -C /usr/java 配置環境變量,將 scala 加入到 PATH 中 vi/etc/profile

exportJAVA_HOME=/usr/java/jdk1.8.0_111
exportPATH=$PATH:$JAVA_HOME/bin:/usr/java/scala-2.11.8/bin

 

2.2 IDEA 安裝

目前 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,點擊下一步即可。由於我們離線安裝插件,所以點擊 SkipAllandSetDefaul 2.下載 IEDA 的 scala 插件,地址 http://plugins.jetbrains.com/?idea_ce

 

2.3 IDEAScala 插件的離線安裝

安裝 Scala 插件: Configure->Plugins->Installpluginfromdisk-> 選擇 Scala 插件 ->OK-> 重啓 IDEA

 

2.4 IDEA 創建 HelloScala 工程

安裝完成後, 雙擊已打開 IDEA, 創建一個新的項目(CreateNewProject)

選中左側的 Scala->IDEA->Next 

輸入項目名稱 -> 點擊 Finish 完成即可

 

3.基本語法

3.1 函數式編程體驗 Spark-Shell 之 WordCount

Q1:對 上 述 文 件 內 容 使 用 Spark進 行 單 詞 個 數 統 計 ?


Q2:對 上 述 輸 出 結 果 進 行 降 序?


3.2 Scala 交互模式(cmd 窗口介紹)

按住win+R,輸入 cmd 命令, 打開 windows 控制檯:

輸入 scala, 進入 Scalashell 交互模式:

 

3.3 數據類型

Scala 和 Java 一樣,有 7 種數值類型 Byte、Char、Short、Int、Long、Float 和 Double(無包 裝類型)和 Boolean、Unit 類型.
注意:Unit 表示無值,和其他語言中 void 等同。用作不返回任何結果的方法的結果類型。Unit 只有一個實例值,寫成()。

3.4 變量的定義

object 變量定義 extends App { 
    /** 
      * 定義變量使用var或者val關 鍵 字 
      *
      * 語法: 
      *  var | val 變量名稱(: 數據類型) =變量值
      */
    // 使用val修飾的變量, 值不能爲修改,相當於java中final修飾的變量 
    val name = "tom"

    // 使用var修飾的變量,值可以修改 
    var age = 18

    // 定義變量時,可以指定數據類型,也可以不指定,不指定時編譯器會自動推測變量的數據類型 
    val name2 : String = "jack"
}

3.5 字符串的格式化輸出

/*
 * Scala 中的格式化輸出
 */

object ScalaPrint extends App {

  val name = "JackMa"
  val price = 998.88d
  val url = "www.baidu.com"
  // 普通輸出,注意這裏是可以使用逗號分隔的,但是在java中,我們是需要用“+”號拼接
  println("name=" + name,"price="+price,"url="+url)

  // 'f'插值器允許創建一個格式化的字符串,類似於C語言中的printf。
  // 在使用'f'插值器時,所有變量引用都應該是printf樣式格式說明符,如%d ,%i ,%f等 。
  // 這裏$name%s打印String變量name,
  println(f"姓名:$name%s,價格:$price%1.2f,網址:$url%s")
  println(f"姓名:%%s,價格:%%1.1f,網址:%%s",name,price,url)

  // 's'插值器允許在字符串中直接使用變量
  // 下列語句中將String型變量($name)插入到普通字符串中
  println(s"name=$name,price=$price,url=$url")

  //'s'插值器還可以處理任意形式的表達式
  println(s"1+1=${1+1}") //output "1+1=2"
}

3.6 條件表達式

/*
 * Scala if條件表達式
 */
object ScalaIf extends App {
   //if語句的使用
    var faceValue=98
    var res1=if (faceValue>90) "帥的一批" else "有點惱火"
    print(res1)

    //3>5 不成立,且代碼沒有else分支,那麼res2應該輸出什麼呢?
    var i=3
    var res2=if (i>5) i
    print(res2)// output  ()代表空

    // 支持嵌套,if...else if ...else代碼過多時可以使用{}
    val score=85
    if(score<60)"不及格"
    else if(score>=60&&score<70)"及格"
    else if (score>=80&&score<90)"優秀"
    else "優秀"
}

3.7 循環語句和yeild 關鍵字

/*
 * Scala for循環
 */
object ScalaFor extends App {
    //  定一個數組
    var arr=Array(1,2,3,4,5,6)

    //遍歷數組中麼個元素
    for(ele <- arr){
      print(ele)
    } /*output:1 2 3 4 5 6*/

    // 0 to 5 =>會生成一個範圍集合Range(0,1,2,3,4,5),左閉右閉
    for(i <- 0 to 5){
      print(i) /*output:0,1,2,3,4,5*/
    }

    // 0 until 5 =>會生成一個範圍集合Range(0,1,2,3,4),左閉右開
    for(i <- 0 until 5){
      print(i) /*output:0,1,2,3,4*/
    }

    // for循環中可以增加守衛,下面這段語句是打印arr數組中的偶數
    for(i <- arr if i%2==0){
      print(i)
    }/*input:2,4,6*/

    //雙層for循環
    for (i <- 1 to 3;j <- 1 to 3 if i!=i){
      print(i*10+j+"")
    }/*output:12,13,21,23,31,32*/

    //  yield 關鍵字將滿足條件的e的值又組合成一個數組
    var arr2=for(e <- arr if e%2==0)
       yield e
    for (i <- arr2){
      print(i)/*output:2,4,6*/
    }
}

3.8 運算符和運算符重載

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

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

例如:

a+b

是如下方法調用的簡寫:

a.+(b)

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

3.9 方法的定義與調用

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

/*
 * 方法的定義及調用
 * 定義方法的格式爲 :
 * def methodName ([listofparameters]) : [ returntype ] = { }
 * 如果不使用等號和方法體,則隱式聲明抽象(abstract)方法 。
 */
object ScalaMethod extends App{
  // 定義一個sum方法,該方法有兩個參數,返回值爲int類型
  def sum(a:Int, b: Int): Int = { a + b }
  // 調用
  val result=sum(1,5)
  print(result)
  // 該方法沒有任何參數 , 也沒有返回值
  def sayHello1 = print("Say BB1")
  def sayHello2() = print("Say BB2")
  sayHello1 // 如果方法沒有()調用時不能加()
  sayHello2() // 可以省略( ) ,也可以不省略
}

方法可轉換爲函數:

3.10 函數的定義與調用

函數定義方式一:

調用:f1(2),其中 f1 爲函數的引用,也可以叫做函數名。function1 表示一個參數的函數。


函數定義方式二:

下面爲沒有任何參數的函數,,函數的返回值爲 Int 類型。

3.11傳值調用與傳名調用

通常,函數的參數是傳值參數;也就是說,參數的值在傳遞給函數之前就已經確定。

但是, 在 Scala 中,我們方法或者函數的參數還可以是一個表達式,也就是將一個代碼邏輯傳遞給了某個方法或者函數。

/*
 *scala的
 *      傳名調用( call-by-name)
 *      傳值調用( call-by-value)
 */
object ScalaCallName  extends App{

    def currentTime():Long={
      System.nanoTime();
    }
    // 該方法的參數爲一個無參的函數,並且函數的返回值爲Long
    def delayed(f: => Long): Unit ={
        print(s"time=${f}")
    }
    
    def delayed2(time: Long): Unit ={
      print(s"time=${time}")
    }

    //調用方式一
    delayed(currentTime())

    // 調用方式二
    // 等價於調用方式一,本質是先計算出參數的值,在帶入方法
    var time=currentTime()
    delayed2(time)

}

3.12 可變參數函數

/*
 *scala的可變參數 
 */
object ScalaVarParams  extends App{
    //定義一個可變參數方法
    def methodManyParams(params:Int*): Unit ={
      for (i <- params){
        print(i)
      }
    }
   // 調用
    methodManyParams(1,2,3,5)
}

3.13 默認參數值函數

/*
 *scala的默認參數
 */
object ScalaDefaultParams  extends App{
    //定義一個默認參數方法
    def add(a:Int=1,b:Int=2): Int ={
       a+b
    }
    // 等價於add(a=3,b=2)
    add(3)
    // 等價於add(a=4,b=5)
    add(4,5)
    // 等價於add(a=1,b=5)
    add(b=5)
}

3.14 高階函數

/*
 *scala的高階函數
 */
object ScalaHM  extends App{
    //  高階函數將其他函數作爲參數
    def apply(f:Int => Int,p:Int): Unit ={
      print(f(p))
    }

    def fn1(a:Int): Int ={
        a*a
    }
    apply(fn1,10)/*output:100*/
}

3.15 部分參數應用函數

如果函數傳遞所有預期的參數,則表示已完全應用它。 如果只傳遞幾個參數並不是全部參 數,那麼將返回部分應用的函數。這樣就可以方便地綁定一些參數,其餘的參數可稍後填寫 補上。

/*
 *scala的部分參數
 */
object ScalaPartParams  extends App{
    //定義了一個求和函數
    def sum(a:Int,b:Int): Int ={
         a+b
    }
    // 調用sum函數的時候,傳入了一個參數a=10,但是b爲待定參數。
    // sumWithTen形成了一個新的函數,參數個數爲一個,類型爲int型,
    def sumWithTen:Int => Int=sum(10,_: Int)

    //sumWithTen(1),本質是sum(10,1)
    print(sumWithTen(1))
}

3.16 柯里化(Currying)

柯里化(Currying)指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新 的函數返回一個以原有第二個參數爲參數的函數。

/*
 *scala的柯里化
 */
object ScalaCurrying  extends App{
    //定義了一個求和函數
    def add(a:Int,b:Int)=a+b
    // 那麼我們在調用的時候,應該是add(1,2)
    // 現在我們將這個函數變形
    def add(a:Int)(b:Int)=a+b
    // 那麼我們在調用的是後應該是add(1)(2),其結果還是等於3,這種方式(或過程)就叫做柯里化,
    // 經過柯里化之後函數通用性降低,但是適用性有所提高
    //分析下其演變過程
    def add(a:Int): Int => Int={
      (b:Int)=>a+b
    }
    //(b:Int)=>a+b 爲一個匿名函數,也就意味着add方法的返回值爲一個匿名函數,
    // 那麼現在的調用過程爲
    var result=add(1)
    var sum1=result(2)

}

3.17 偏函數

被包在花括號內沒有 match 的一組 case 語句是一個偏函數,它是 PartialFunction[A,B]的一個 實例,A 代表參數類型,B 代表返回類型,常用作輸入模式匹配。

/*
 *scala的偏函數
 */
object ScalaPartialFunc  extends App{
    /*
     *PartialFunction[A,B],A 代表參數類型,B 代表返回類型,常用作輸入模式匹配。 
     */
    def fun1:PartialFunction[String,Int]={
      case "one" => 1
      case "two" => 2
      case _ => -1
    }
}

4.集合的使用

Scala 的集合有三大類:序列 Seq、集 Set、映射 Map,所有的集合都擴展自 Iterable 特質 在 Scala 中集合有可變(mutable)和不可變(immutable)兩種類型,immutable 類型的集合 初始化後就不能改變了(注意與 val 修飾的變量進行區別。

4.1 定長數組和變長數組

/*
 *scala數組
 */
import scala.collection.mutable.ArrayBuffer
object ScalaArray{
  def main(args: Array[String]): Unit = {
    // 初始化一個長度爲8的定長數組,所有元素均爲0
    val arr=new Array[Int](8)
    // 直接打印數組的到的是數組的hashcode值
    print(arr)
    // 將數組轉換成數組緩衝,就可以看到數組中內容
    print(arr.toBuffer)
    // 定義一個長度爲3的定長數組
    val arr2=Array("tom","jack","oliver")
    // 通過索引獲取數組中的值
    print(arr2(1))
    // 創建一個變長數組
    var arr3=ArrayBuffer[Int]()
    // 末尾追加1
    arr3+=1
    //  追加多個元素
    arr3+=(2,3,4,5)
    //  追加一個數組
    arr3++=Array(6,7)
    //  追加一個變長數組
    arr3++=ArrayBuffer(8,9)
    //  打印
    arr3.foreach(print)
    // 在某個位置插入元素
    arr3.insert(0,-1,0)
    //  移除指定索引元素
    arr3.remove(0,1)
  }
}

4.2 map|flatten|flatMap|foreach 方法的使用

object ScalaArray{
  def main(args: Array[String]): Unit = {
    val arr=Array(1,2,3,4,5)
    // map函數將arr數組中所有元素進行某種映射操作,
    // (x:Int)=>x*2爲一個匿名函數,x就是數組中的每個元素
    val z= arr map((x:Int)=>x*2)
    // 或者這樣寫,編譯器會推斷數據的類型
    val y=arr.map(x=>x*2)
    // 亦或者,_表示入參,代表數組中每個元素
    val x=arr.map(_ * 2)
    
    println("--------騷氣分割線-----------")

    val words=Array("tom jack oliver jack","hello tom oliver tom ")
    // 將words中元素按照“,”切分
    val splitWords: Array[Array[String]] = words.map(x=>x.split(","))
    //此時數組中的每個元素進行split操作之後變成了Array,
    // flatten是對splitWords裏面的元素進行扁平化操作
    val flatten: Array[String] = splitWords.flatten

    // 上訴兩步操作可以等價於flatmap,意味着先map操作之後進行flatten操作
    val result=words.flatMap(_ .split(","))
    
    // 遍歷數組,打印每個元素
    result.foreach(print)
  }
}

4.3 Seq 序列

不可變的序列 importscala.collection.immutable._

在 Scala 中列表要麼爲空(Nil 表示空列表)要麼是一個 head 元素加上一個 tail 列表。 9::List(5,2) :: 操作符是將給定的頭和尾創建一個新的列表

object ScalaListTest{
  def main(args: Array[String]): Unit = {
      // 創建一個不可變集合
      val list=List(1,2,3)
      // 創建一個可變集合
      val mutalist=ListBuffer(1,2,3)
      // 將0插入到list前面生成一個新的list
      val list1: List[Int] = list.:+ (0)
      val list2: List[Int] = 0+:list
      val list3: List[Int] = 0::list
      val list4: List[Int] = list.::(0)
      // 將一個元素添加到list後面,形成新的list5
      val list5: List[Int] = list:+(4)

      val list6=List(4,5,6)
      // 將兩個list合併成一個新的list
      val list7: List[Int] = list++list6
      val list8: List[Int] = list ++: list6
      // 將list6插入到list前面形成一個新的集合
      val list9: List[Int] = list.:::(list6)
      list8.foreach(println)
  }
}

4.4 Set 集

可變的set

object ScalaSetTest{
  def main(args: Array[String]): Unit = {
    // 創建一個可變set
    val set: mutable.HashSet[Int] = new mutable.HashSet[Int]()
    // 往set中加入元素1
    set+=1
    // add等價於+=
    set.add(2)
    // 添加另一個set中元素
    set ++= Set(7,8,9)
    // 移除一個元素
    set-=2
    // remove等價於-=
    set.remove(2)
    
    set.foreach(print)
  }
}

不可變的set

object ScalaSetTest{
  def main(args: Array[String]): Unit = {
    // 創建一個不可變set
    val set: HashSet[Int] = new HashSet[Int]()
    // 將元素和原來的set合併生成一個新的set2,原有set不變
    val set2=set+1
    // 兩個set合併生成一個新的set
    val set3=set++Set(4,5,6)
    set3.foreach(print)
  }
}

4.5  集合常用的方法

map, flatten, flatMap, filter, sorted, sortBy, sortWith, grouped, fold(摺疊), foldLeft, foldRight, reduce, reduceLeft, aggregate, union, intersect(交集), diff(差集), head, tail, zip, mkString, foreach, length, slice, sum

scala> val list=List(1,2,3,4,5)
list: List[Int] = List(1, 2, 3, 4, 5)

scala> list.map(x=>x*2)
res0: List[Int] = List(2, 4, 6, 8, 10)

scala> list.sortBy(x=> -x)
res5: List[Int] = List(5, 4, 3, 2, 1)

scala> val words=List(("a",3),("b",5),("c",2))
words: List[(String, Int)] = List((a,3), (b,5), (c,2))

scala> words.sortBy(t=> t._2)
res6: List[(String, Int)] = List((c,2), (a,3), (b,5))

scala> words.sortWith((x,y)=>x._2>y._2)
res7: List[(String, Int)] = List((b,5), (a,3), (c,2))

scala> list.grouped(2).toList
res10: List[List[Int]] = List(List(1, 2), List(3, 4), List(5))

scala> list
res13: List[Int] = List(1, 2, 3, 4, 5)

scala> list.fold(0)((x,y)=>x+y)
res14: Int = 15

scala> list.fold(0)(_+_)
res15: Int = 15

scala> list.foldLeft(2)(_ - _)
res18: Int = -13

scala> list.foldRight(2)(_ - _)
res19: Int = 1

scala> list.reduce((x,y)=> x+y)
res20: Int = 15

scala> list.aggregate(0)(_ + _,_ + _)
res21: Int = 15

scala> val list=List(1,2,3,4,5)
list: List[Int] = List(1, 2, 3, 4, 5)

scala> val list2=List(1,3,5,7,8)
list2: List[Int] = List(1, 3, 5, 7, 8)

scala> list.union(list2)
res0: List[Int] = List(1, 2, 3, 4, 5, 1, 3, 5, 7, 8)

scala> list.intersect(list2)
res1: List[Int] = List(1, 3, 5)

scala> list.diff(list2)
res2: List[Int] = List(2, 4)

scala> list.head
res3: Int = 1

scala> list.tail
res4: List[Int] = List(2, 3, 4, 5)

scala> list.zip(list2)
res5: List[(Int, Int)] = List((1,1), (2,3), (3,5), (4,7), (5,8))

scala> list.mkString("|")
res7: String = 1|2|3|4|5

scala> list.foreach(print)
12345

scala> list.length
res11: Int = 5

scala> list.slice(1,3)
res12: List[Int] = List(2, 3)

scala> list.sum
res14: Int = 15

4.6並行化集合 par

調用集合的 par 方法, 會將集合轉換成並行化集合

object ScalaParTest{
  def main(args: Array[String]): Unit = {
    val list: List[Int] = List(1,7,9,8,0,3,5,4,6,2)
    val par: ParSeq[Int] = list.par
  }
}

4.7 Map 和 Option

在Scala中Option類型樣例類用來表示可能存在或也可能不存在的值(Option的子類有Some 和 None)。Some 包裝了某個值,None 表示沒有值。

object ScalaMapTest{
  def main(args: Array[String]): Unit = {
    val map: Map[String, Int] = Map("a"->1,"b"->2,"c"->3)
    val i: Int = map("d")
    // map的get方法返回值Option,意味着maybeInt可能取到值也可能沒有取到值
    val maybeInt: Option[Int] = map.get("d")
    // 如果maybeInt=None時會拋出異常
    val v=maybeInt.get
    // 第一個參數爲要獲取的key
    // 第二個參數爲如果沒有這個key返回一個默認值
    val ele: Int = map.getOrElse("d",-1)
  }
}

4.8 案例 wordCount

object ScalaWordCount{
  def main(args: Array[String]): Unit = {
      // 第一種方式
      val words=Array("hello tom jack oliver","tom jack jim hello")
      val strings: Array[String] = words.flatMap(_ .split(" "))
      val tuples: Array[(String, Int)] = strings.map((_ ,1))
      val stringToTuples: Map[String, Array[(String, Int)]] = tuples.groupBy(_._1)
      val stringToInt = stringToTuples.mapValues(_.length)
      stringToInt.foreach(print)
      // 第二種方式
      val list= words.flatMap(_.split(" "))//對數組中每個元素進行切分,並進行扁平化操作
        .map((_,1))    // 將數組中每個元素轉換成元組,元組第二個元素爲1
        .groupBy(_._1) // 將相同單詞元組分爲一組
        .mapValues(_.length)//對每個key的value集合進行長度操作
        .toList //  轉換成list
      list.foreach(print)
      //  第三種方式
      val list2: List[(String, Int)] = words.flatMap(x=>x.split(" ")).groupBy(x=>x).map(t=>(t._1,t._2.length)).toList
      list2.foreach(print)
  }
}

5. 面向對象

5.1 scala 單例對象

在 Scala 中,是沒有 static 這個東西的,但是它也爲我們提供了單例模式的實現方法,那 就是使用關鍵字 object,object 對象不能帶參數。

/*
 * 單例對象
 */
object ScalaSingleton{
    def saySomeThing(msg:String): Unit ={
      println(msg)
    }
}

object test{
  def main(args: Array[String]): Unit = {
    ScalaSingleton.saySomeThing("hello")
    println(ScalaSingleton)
    println(ScalaSingleton)
    // output:
    // hello
    //ScalaSingleton$@ea4a92b
    //ScalaSingleton$@ea4a92b
  }
}

5.2 scala 類

5.2.1 | 類定義 | 主構造器 | 輔助構造器

類定義

/*
 * scala中,類並不用聲明爲public
 * 如果你沒有定義構造器,類會有一個默認的空參構造器
 *
 * var 修飾的變量,對外提供setter、getter方法
 * val 修飾的變量,對外只提供getter方法,不提供setter方法
 */
class Student{
  // _表示一個佔位符,編譯器會根據你變量的類型賦予相應的初始值
  var name:String=_
  // 錯誤代碼,val修飾的變量不能使用佔位符
  // val age:Int=_
  val age:Int=10
}

object test{
  def main(args: Array[String]): Unit = {
    // 空參構造器可以加()也可以不加
    val student = new Student()
    student.name="JackMa"
    //  錯誤代碼,類中使用val修飾的變量不能更改
    //  student.age=20
    println(s"name=${student.name},age=${student.age}")
  }
}

定義在類後面的爲類主構造器, 一個類可以有多個輔助構造器

/*
 * 定義在類名稱後面的構造器爲主構造器
 * 類的主構造器中的屬性會定義成類的成員變量
 * 類的主構造器中屬性沒有被var|val修飾的話,該屬性不能訪問,相當於對外沒有提供get方法
 * 如果屬性使用var修飾,相當於對外提供set和get方法
 */
class Student1(var name:String,val age:Int){
  var gender:String=_
  def this(name:String,age:Int,gender:String){
      this(name,age)
      this.gender=gender
  }
}

object test{
  def main(args: Array[String]): Unit = {
    val s1 = new Student1("Tom",18)
    println(s"name=${s1.age},age=${s1.age},gender=${s1.gender}")
    val s2 = new Student1("JackMa",20,"man")
    println(s2.gender)
    // output:
    // name=18,age=18,gender=null
    // man
  }
}

5.2.2 訪問權限

  • 構造器的訪問權限
/*
 * private 加在主構造器之前,這說明該類的主構造器是私有的,外部對象或者外部類不能訪問
 * 也適用與輔助構造器
 */
class Student2 private(var name:String,val age:Int){
  var gender:String=_
  private def this(name:String,age:Int,gender:String){
      this(name,age)
      this.gender=gender
  }
}
  • 成員變量的訪問權限
/*
 * private val age
 * age 在本類中有setter和getter方法
 * 但是加上private 也就意味着age只能在這個類的內部及其伴生類中可以修改
 */
class Student3 private(){
    private var name:String=_
    // 伴生類可以訪問
    private var age:Int=_
    // private [this]關鍵字標識給屬性只能在類內部訪問,伴生類不能訪問
    private [this] var gender:String="man"
}
// 伴生類
object Student3{
  def main(args: Array[String]): Unit = {
    val student = new Student3()
    // 伴生對象可以訪問
    student.name="jack"
    student.age =20
    println(s"name=${student.name},age=${student.age}")
    // 伴生類不能訪問
    //println(student.gender)
    // output:
    // name=jack,age=20

  }
}
  • 類包的訪問權限
/*
 * private [this] class放在類聲明最前面,是修飾類的訪問權限,也就是說類在某些包下可見或不能訪問
 * private [sheep] class代表該類在sheep包及其子包下可見,同級包不能訪問
 */
private [this] class Student4 (val name:String,private var age:Int){
      var gender :String=_
}
// 伴生類
object Student4{
  def main(args: Array[String]): Unit = {
    val s = new Student4("JackMa",18)
    print(s.age)
  }
}

5.2.3 伴生類 | apply 方法

在 Scala 中, 當單例對象與某個類共享同一個名稱時,他被稱作是這個類的伴生對象。必須 在同一個源文件裏定義類和它的伴生對象。類被稱爲是這個單例對象的伴生類。類和它的伴生對象可以互相訪問其私有成員。

/*
 * 定義一個apply方法
 * object對象中可以對apply進行各種重載
 */
object test{
  def main(args: Array[String]): Unit = {
    val v = test(2,3)// 語法糖
    print(v)
    //output:5
  }
  def apply(a:Int,b:Int)={
     a+b
  }
}

5.3 Trait

ScalaTrait(特質) 相當於 Java 的接口,實際上它比接口還功能強大。 與接口不同的是,它還可以定義屬性和方法的實現。 一般情況下 Scala 的類只能夠繼承單一父類,但是如果是 Trait(特質) 的話就可以繼承多個,實現了多重繼承。使用的關鍵字是 trait。

trait ScalaTrail {
   // 定義了一個屬性
  var pro:Int=666

  // 定義一個沒有實現的方法
  def sayHello(name:String)

  // 定義了一個帶具體實現的方法
  def small(name:String): Unit ={
    println(s"太陽對${name}笑")
  }
}

object ScalaTrailImpl extends ScalaTrail {
  // 實現方法時可以有override關鍵字,也可以沒有
  def sayHello(name: String): Unit = {
    println(s"hello $name")
  }
  // 重寫方法時必須得有override關鍵字
  override def small(name: String): Unit = {
    println(s"$name like small")
  }
}

object test extends App{
  ScalaTrailImpl.sayHello("Oliver")
  // 如果ScalaTrailImpl沒有重寫small方法,則調用ScalaTrail中已經實現了的方法
  // 如果ScalaTrailImpl重寫了small方法,則調用的是ScalaTrailImpl中的方法
  ScalaTrailImpl.small("wang")
  // output:
  // hello Oliver
  // wang like small
}

動態混入特質,使用with關鍵字

trait ScalaTrail {
  // 定義了一個屬性
  var pro:Int=666
  // 定義一個沒有實現的方法
  def sayHello(name:String)
  // 定義了一個帶具體實現的方法
  def small(name:String): Unit ={
    println(s"太陽對${name}笑")
  }
}
class Student {

}

object test extends App{
  val student: Student with ScalaTrail = new Student with ScalaTrail {
    override def sayHello(name: String): Unit = {
      println(f"name=$name")
    }
  }
  student.sayHello("Jack")
}

5.4 抽象類

在 Scala 中, 使用 abstract 修飾的類稱爲抽象類. 在抽象類中可以定義屬性、未實現的方法和 具體實現的方法。


abstract class Animal{
  // 定義了一個屬性
  var name:String="animal"
  // 定義一個未實現方法
  def sleep()
  // 定義一個帶具體實現方法
  def eat(f:String): Unit ={
    println(s"eating $f")
  }
}

5.5 繼承

繼承是面向對象的概念,用於代碼的可重用性。 被擴展的類稱爲超類或父類, 擴展的類稱 爲派生類或子類。Scala 可以通過使用 extends 關鍵字來實現繼承其他類或者特質。

/*
 * with 後面只能是特質
 * 父類未實現的方法,子類必須實現
 * 父類已經實現的方法,子類要重寫該方法,必須使用override關鍵字
 */
abstract class Animal{
  // 定義了一個屬性
  var name:String="animal"
  // 定義一個未實現方法
  def sleep()
  // 定義一個帶具體實現方法
  def eat(f:String): Unit ={
    println(s"eating $f")
  }
}
class Dog extends Animal {
  override def sleep(): Unit = {
    println("耳朵貼地睡")
  }
}
object test extends App{
  private val dog = new Dog
  dog.sleep();
  dog.eat("bone")
  // output:
  // 耳朵貼地睡
  // eating bone
}

5.5.1final 關鍵字

 

  • 被 final 修飾的類不能被繼承;
  • 被 final 修飾的屬性不能重寫;
  • 被 final 修飾的方法不能被重寫。

5.5.2 type 關鍵字

Scala 裏的類型,除了在定義 class,trait,object 時會產生類型,還可以通過 type 關鍵字來聲明 類型。
type 相當於聲明一個類型別名:

object test extends App{
   // 把String類型用S代替
  type S = String
  var name : S= "Oliver"
  println(name)
}

通常 type 用於聲明某種複雜類型,或用於定義一個抽象類型。

class A{
  type T
  def fn(t:T): Unit ={
    println(t)
  }
}
class B extends A{
  override type T = Int
}
class C extends A{
  override type T = String
}
object test extends App{
  private val b = new B()
  private val c = new C()
  b.fn(3)
  c.fn("Hello")
}

5.6 樣例類/樣例對象

/*
 * 樣例類,使用case關鍵字修飾的類,其重要的就是支持模式匹配
 * 樣例類:case class 類名(屬性)
 * 類名定義必須是駝峯式,屬性名稱第一個字母小寫
 */
case class Message(sender:String,massageContent:String)
/*
 * 樣例對象不能封裝數據
 */
case object CheckHeartBeat

 

6. 模式匹配 match case

6.1 匹配字符串/類型/守衛

object test extends App{
  /*
   * 匹配字符串
   */
  def contentMatch(str:String)=str match {
      case "dog"=>println("小狗")
      case "cat"=>println("小貓")
      case "1"=>println("數字1")
      case _ => println("匹配失敗")
  }
  contentMatch("cat")
  contentMatch("1")
  contentMatch("2")
  //  output:
  //  小貓
  //  數字1
  //  匹配失敗
  /*
   * 匹配類型
   */
  def typeMatch(ele:Any)=ele match {
      case x:Int=>println(s"Int:${x}")
      case y:Double=>println(s"Double:${y}")
      case z:String=>println(s"String:${z}")
      case _ =>println("match failure")
  }
  typeMatch("helo")
  typeMatch(2)
  typeMatch(2d)
  //  output:
  //  String:helo
  //  Int:2
  //  Double:2.0
  

6.2 匹配數組

object test extends App{
  /*
   * 匹配數組
   */
  def arrayMatch(arr:Any)=arr match {
      case Array(0)=>println("只有一個0元素的數組")
      case Array(0,_)=>println("以0開頭,擁有兩個元素的數組")
      case Array(1,_,3)=>println("以1開頭,3結尾的任意三個元素的數組")
      case Array(_*)=>println("N個元素的數組")
  }
  arrayMatch(Array(0))
  arrayMatch(Array(0,2))
  arrayMatch(Array(1,true,3))
  arrayMatch(Array(1,3,5,7,9))
  // output:
  // 只有一個0元素的數組
  // 以0開頭,擁有兩個元素的數組
  // 以1開頭,3結尾的任意三個元素的數組
  // N個元素的數組
}

6.3 匹配集合

object test extends App{
  /*
   * 匹配集合
   */
  def listMatch(list:Any)=list match {
    case 0::Nil=>println("只有0元素的集合")
    case 7::9::Nil=>println("只有7和9兩個元素的集合")
    case x::y::z::Nil=>println(s"只有三個元素集合${x},${y},${z}")
    case m::n=>println(s"擁有head和tail的集合。head:${m},tail:${n}")
  }
  listMatch(List(0))
  listMatch(List(7,9))
  listMatch(List(1,2,3))
  listMatch(List(8,7,6,5,4))
  // output:
  // 只有0元素的集合
  // 只有7和9兩個元素的集合
  // 只有三個元素集合1,2,3
  // 擁有head和tail的集合。head:8,tail:List(7, 6, 5, 4)
}

6.4 匹配元組

object test extends App{
  /*
   * 匹配元組
   */
  def tupMatch(tup:Any)=tup match {
    case (3,x,y)=>println("第一個元素爲3的元組")
    case (_,2)=>println("第二個元素爲2,擁有兩個元素的數組")
    case (x,y,z)=>println("擁有三個元素的任意元組")
  }
  tupMatch((3,2,1))
  tupMatch((3,2))
  tupMatch((4,2,1))
  //  output:
  //  第一個元素爲3的元組
  //  第二個元素爲2,擁有兩個元素的數組
  //  擁有三個元素的任意元組
}

6.5 匹配樣例類/樣例對象

case object CheckTimeOutTask
case class SubmitTask(id:String,name:String)
case class HeartBeat(time:Long)
object test extends App{
  /*
   * 匹配樣例類,樣例對象
   */
  def coMatch(ele:Any)=ele match {
    case SubmitTask(id,name)=>println(s"submit task-id:${id},task-name:${name}")
    case CheckTimeOutTask=>println("checking.....")
    case HeartBeat(time)=>println(s"time is ${time}")
  }
  coMatch(SubmitTask("001","node1"))
  coMatch(CheckTimeOutTask)
  coMatch(HeartBeat(8888888L))
  coMatch(HeartBeat(6666))
  // output:
  // submit task-id:001,task-name:node1
  // checking.....
  // time is 8888888
  // time is 6666
}

 

 

7.Scala 高級語法

 

7.1 隱式(implicit)詳解

  • 隱式參數 
  • 隱式轉換類型
  • 隱式類

7.1.1 隱式參數

定義方法時,可以把參數列表標記爲 implicit,表示該參數是隱式參數。一個方法只會有一 個隱式參數列表,置於方法的最後一個參數列表。如果方法有多個隱式參數,只需一個 implicit 修飾即可。
譬如:deffire(x:Int)(implicita:String,b:Int=9527)
當調用包含隱式參數的方法是,如果當前上下文中有合適的隱式值,則編譯器會自動爲該 組參數填充合適的值,且上下文中只能有一個符合預期的隱式值。如果沒有編譯器會拋出 異常。當然,標記爲隱式參數的我們也可以手動爲該參數添加默認值。

object test extends App{
    def say(implicit content:String)=println(content)
    /*
     * say方法時隱式參數,如果你沒有傳參數
     * 編譯器在編譯的時候會自動的從當前上下文中找一個隱式值(符合參數類型的隱式值)
     */
    say("good morning")
    implicit val content="Are you ok ?"
    say
    //  output:
    //  good morning
    //  Are you ok ?
    
}

7.1.2 隱式的轉換類型

使用隱式轉換將變量轉換成預期的類型是編譯器最先使用 implicit 的地方。當編譯器看到類 型 X 而卻需要類型 Y,它就在當前作用域查找是否定義了從類型 X 到類型 Y 的隱式定義。

object test extends App{

  //var i:Int=3.14/*此時程序會報錯*/
    implicit def double2Int( d:Double)=d.toInt
  // i是int類型,但是賦值的時候缺是一個浮點型,此時編譯器會在當前上下文中找一個隱式轉換,
  // 找一個double轉換成int的隱式轉換
    var i:Int=3.14
    println(i)

}

7.1.3 隱式類

在 Scala 中,我們可以在靜態對象中使用隱式類。


object test extends App{

  implicit class Calculator(x:Int){
    def add(y:Int)={
      x+y
    }
  }
  var z:Int=5
  println(5.add(3))
}

7.2 泛型

通俗的講,比如需要定義一個函數,函數的參數可以接受任意類型。我們不可能一一列舉所 有的參數類型重載函數。

那麼程序引入了一個稱之爲泛型的東西,這個類型可以代表任意的數據類型。

例如 List,在創建 List 時,可以傳入整形、字符串、浮點數等等任意類型。那是因爲 List 在 類定義時引用了泛型

/*
 *scala枚舉類型
 */
object ClothesEnum extends Enumeration{
  type ClothesEnum=Value
  val 衣服,褲子,衛衣 =Value
}
/*
 * 定義了一個泛型類
 */
class Massage[T]{
  def getMassage(s:T): Unit ={
    println(s)
  }
}
/*
 * 子類繼承指明類型
 */
class strMassage[String] extends Massage{
    
}
object test extends App{
  private val msg = new Massage[String]
  msg.getMassage("有內鬼,終止交易")
}

7.3 類型約束

7.3.1 上界(UpperBounds)/下界(lowerbounds)

  • UpperBounds

在 Java 泛型裏表示某個類型是 Test 類型的子類型,使用 extends 關鍵字:

<T extends Test>

//或用通配符的形式: 
<? extends Test>

這種形式也叫 upperbounds(上限或上界),同樣的意思在 Scala 的寫法爲:

[T <: Test]

//或用通配符: 
[_ <: Test]

 

  • LowerBounds

在 Java 泛型裏表示某個類型是 Test 類型的父類型,使用 super 關鍵字:

<T super Test>

//或用通配符的形式: 
<? super Test>

這種形式也叫 lowerbounds(下限或下界),同樣的意思在 scala 的寫法爲:

[T >: Test]

//或用通配符: 
[_ >: Test]

7.3.2 視圖界定/上下文界定

  • Viewbounds

<% 的意思是“viewbounds”(視界),它比  <: 適用的範圍更廣,除了所有的子類型,還允許隱式轉換過去的類型。

def method[A <% B](arglist):R=...

等價於:
def method[A](arglist)(implicit viewAB:A=>B):R=...
或等價於: 
implicit def conver(a:A):B= …
<% 除了方法使用之外,class 聲明類型參數時也可使用: 
class A[T<%Int]
  • Contextbounds

與 viewbounds 一樣 contextbounds(上下文界定)也是隱式參數的語法糖。爲語法上的方便, 引入了”上下文界定”這個概念。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章