Scala學習筆記【從入門到放棄】

 

一、關於Scala安裝

https://www.scala-lang.org/download/

 

如果你是Java程序員,想學習Scala,請看官網提供的快速入門:

https://docs.scala-lang.org/tutorials/scala-for-java-programmers.html

 

二、Scala語言Hello World的編寫

object HelloWorld{
    def main(args: Array[String]){
        println("Hello World")   //Scala每行不強求使用使用;
    }
}

Java程序員應該熟悉這個程序的結構:它包含一個調用main命令行參數的方法,一個字符串數組作爲參數; 此方法的主體包含對預定義方法的單個調用println ,其中"Hello World"作爲參數。該main方法沒有返回值(它是一個過程方法)。因此,沒有必要聲明返回類型。

Java程序員不太熟悉的是object 包含該main方法的聲明。這樣的聲明引入了通常所說的單例對象即具有單個實例的類。因此,上面的聲明聲明瞭一個被調用的類HelloWorld和該類的一個實例,也被稱爲HelloWorld。該實例是在第一次使用時按需創建的。

精明的讀者可能已經注意到該main方法未在static處聲明。這是因爲Scala中不存在靜態成員(方法或字段)。Scala程序員不是定義靜態成員,而是在單例對象中聲明這些成員。

 

編譯

如果我們將上述程序保存在一個名爲的文件中 HelloWorld.scala,我們可以通過發出以下命令來編譯它(大於號>表示shell提示符):我們使用 `scalac` Scala編譯器,它生成的目標文件是標準的Java類文件。

> scalac HelloWorld.scala

運行

使用該scala命令運行Scala程序。它的用法與java用於運行Java程序的命令非常相似,並且接受相同的選項。

> scala HelloWorld

Hello World

 

三、Scala入門

3.1 val 和 var 的區別

val:值          (類似於Java裏面的final) 值不可變

標準格式   val 值名稱 : 類型 = 值大小

//標準寫法 
val age : Int = 20

//寫法1 大部分情況Scala可以識別(即省略  :類型)
val age = 20

 

var:變量     (類似於Java裏面的變量) 值可變

標準格式   val 值名稱 : 類型 = 值大小

總結:當工作中用到常量(值不需要改變時)用val , 用到變量(值需要被改變)則用var。

 

3.2 Scala九大基本數據類型【類型轉換、類型判斷】

Scala與Java一樣,有9大基本數據類型:

Byte、Char、Short、Int、Long、Float、Double、Boolean 【注意都爲大寫開頭】 和 Unit

注意:Unit表示無值,和其他語言中的void等同。

用作不返回任何結果的方法的結果類型。Unit只有一個實例值,寫成()

Any相當於Java中的Object,Anyval就是我們上面所屬的8大數據類型+Unit,其他爲AnyRef。

 

 

基本使用就是這樣子,很簡單,但有幾個注意點,看下圖:

1、數據類型都爲大寫字母開頭,不同於Java的基本數據類型爲小寫。

2、如何命名一個Float類型的常量呢?

信息1:默認爲Double    信息2:需要聲明爲Float類型時,必須在後面加上"f"

當然這樣也是可以的:

3、如何進行基本數據類型的轉換呢?————asInstanceOf[轉換後的類型]

4、如何進行基本數據類型的判斷呢?————isInstanceOf[是否爲該類型]

 

3.3 lazy在Scala中的應用【延遲加載】

如果一個變量或常量聲明爲lazy,並不會立刻發生計算輸出,只有第一次使用到該變量或常量時候,纔會計算返回結果

使用場景:通常使用在耗費資源的計算、佔用大量網絡/文件IO等情況

注意當使用lazy時候,如文件路徑寫錯了,並不會立刻報錯,而是使用到該變量時候才報錯

 

 

3.4 如何使用IDEA整合Maven構建Scala程序

下一步---Finish

Plus:爲什麼在IDEA New中找不到Scala Class呢?

請在Plugin中點擊Install Jetrains plugin... 在裏面搜索Scala並下載安裝即可,重啓IDEA。

設置Scala-SDK ,添加電腦安裝的Scala路徑,Scala Class是不是出來拉?

最後附上scala入門程序的pom.xml配置

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cn.itcats</groupId>
  <artifactId>myscala</artifactId>
  <version>1.0-SNAPSHOT</version>
  <inceptionYear>2008</inceptionYear>
  <properties>
    <scala.version>2.11.8</scala.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.scala-lang</groupId>
      <artifactId>scala-library</artifactId>
      <version>${scala.version}</version>
    </dependency>
  </dependencies>

  <build>
    <sourceDirectory>src/main/scala</sourceDirectory>
    <testSourceDirectory>src/test/scala</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.scala-tools</groupId>
        <artifactId>maven-scala-plugin</artifactId>
        <version>2.15.2</version>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>testCompile</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

 

 

四、Scala方法與函數

在Scala中方法與函數是不一樣的

4.1    方法的定義和使用

例子說明:

object FunctionTest {
  //main方法 Unit代表無返回值
  def main(args: Array[String]): Unit = {

    println(add1(1, 1))

    println(add2(1, 1))
    //方式3
    println(add3())
    //方式4 (等同於方式3,當無入參時,可以省略括號)
    println(add3)

    sayHello
  }

  //得到 a + b 結果(方式1)
  def add1(a: Int, b: Int): Int = {
    a + b //最後一行就是返回值,不需要手動書寫return
  }

  //得到 a + b 結果(方式2)
  //如果只有一行  {} 可以省略
  //另外省略了方法的返回值類型,Scala會猜測返回值類型(Int + Int 那肯定還是Int呀)。實際該方式與方式1相同
  def add2(a: Int, b: Int) = a + b


  //得到 1 + 1 結果(方式3)————無入參
  def add3() = 1 + 1   //省略了方法返回值類型,由於只有一行,省略了{}

  def sayHello: Unit ={
    println("Hello")
  }
  
}

 

4.2    函數的定義與調用

f的定義方式1:

scala> var f1 = (x: Int ,y: Int) => x * y
f1: (Int, Int) => Int = <function2>

scala> f1(9,9)
res9: Int = 81

 

函數的定義方式2:

scala> val f2:(Int,Int) => Int = (x,y) => x * y
f2: (Int, Int) => Int = <function2>

scala> f2(9,9)
res10: Int = 81

 

函數的定義方式3:

方法變爲函數  ——  方法名 _      【空格不能省略】

scala> def add2(a: Int, b: Int) = a + b
add2: (a: Int, b: Int)Int

scala> add2 _
res7: (Int, Int) => Int = <function2>

含義爲:<function2>代表 add _   返回是一個函數,函數擁有兩個參數【因爲add方法有兩個參數,返回的函數也是兩個參數】。左側(Int,Int)是函數的參數列表, => 是函數的重要標誌 , 右側Int表示函數的返回值是Int類型

 

 

4.3    傳值調用與傳名調用

通常,函數的參數是傳值參數; 也就是說,參數的值在傳遞給函數之前確定。
其實, 在 Scala 中, 我們方法或者函數的參數可以是一個表達式, 也就是將一個代碼邏輯傳遞給了某個方法或者函數.

案例1:

object CallByValueOrName {
  var money: Double = 100.00;

  //定義支付方法
  def pay(): Unit = {
    money -= 5;
  }

  //查詢當前餘額(先支付一次,再查餘額)
  def queryMoney(): Double = {
    pay()
    money
  }
  
  //傳值調用
  def callByValue(x: Double): Unit = {
    for (i <- 0 until (3)) { // 0 1 2
      print(s"money爲${x}  ") //money爲95.0  money爲95.0  money爲95.0
    }
  }

  //傳名(函數)調用 x: => Int 表示的是一個方法的簽名 => 是一個沒有參數,返回值爲Int類型的函數
  def callByName(x: => Double): Unit = {
    for (i <- 0 until (3)) {
      print(s"money爲${x}  ") //money爲90.0  money爲85.0  money爲80.0  
    }
  }

  def main(args: Array[String]): Unit = {
    /** 傳值調用 (參數爲一個具體的數值)
      * 1、計算queryMoney的返回值 = 95.0
      * 2、將95.0作爲參數傳遞給callByValue方法
      */
    callByValue(queryMoney)

    /** 傳名(函數)調用
      * 實際上是將queryMoney方法名稱傳遞到callByName內部執行
      */
    callByName(queryMoney)
  }
}

案例2:

object Calculate {
  //add方法有兩個Int類型的參數,返回值爲Int類型
  def add(a: Int, b: Int): Int = {
    a + b
  }

  //add2方法有三個參數,第一個參數是一個函數(實際上是一個函數的簽名,對函數入參個數、類型、返回值類型的約束),
  //第二個參數和第三個參數爲Int類型的參數
  //第一個參數是有兩個Int類型參數,返回值類型爲Int類型的函數
  def add2(x: (Int, Int) => Int, a: Int, b: Int) = {
    x(a, b) // x(1, 2) => 1 + 2
  }

  def add3(x: Int => Int, b: Int) = {
    x(b) + b //b * 10 + b
  }

  //兩個參數爲Int,返回值爲Int的函數
  val f1: (Int, Int) => Int = (a, b) => a + b
  val f2 = (a: Int, b: Int) => a + b


  //一個參數爲Int,返回值爲Int的函數
  val f3 = (a: Int) => a * 10

  def main(args: Array[String]): Unit = {
    //傳值函數
    var res1 = add(1, 2 + 2); //執行過程 add(1,4)

    //傳名函數
    add2((a, b) => a + b, 1, 2) // 3
    add2(f1, 1, 2) // 3 與上面等效
    add2(f2, 1, 2) // 3 與上面等效

    //f3(8) + 8
    //8 * 10 + 8
    add3(f3, 8) //88
  }
}

 

4.4    默認參數的使用

所謂的默認參數就是:在函數定義時,允許指定參數的缺省值,例子如下:

object DefaultParam extends App {

  def printMessage(name: String = "itcats", age: Int = 18, country: String = "中國"): Unit = {
    println(s"$name $age $country")
  }

  printMessage()
  printMessage("zp")
  printMessage(age = 20, country = "美國")
}

 

4.5    命名參數的使用【不推薦使用】

調用方無序嚴格按照函數入參的順序傳遞參數,只需使用命名參數即可隨意調換入參的順序,例子:

object Function2 {
  def main(args: Array[String]): Unit = {
    //常規調用方式
    speed(100, 10)
    //命名參數調用方式1
    speed(distance = 100, time = 10)
    //命名參數調用方式2 (調換順序)
    speed(time = 10, distance = 100)
  }

  //演示命名參數
  def speed(distance: Int, time: Int): Unit = {
    println(distance / time)
  }
}

 

 

4.6    可變參數的使用

object Function3 {

  def main(args: Array[String]): Unit = {
    println(sum(1, 2, 3, 4))
  }

  //演示可變參數 在參數類型後加一個通配符* 即可
  def sum(nums: Int*): Int = {
    var res = 0;
    for (num <- nums) {
      res += num;
    }
    res
  }

  //可變參數要放在參數列表中最後的位置
  def sum1(initValue: Int, nums: Int*): Int = {
    var res = initValue;
    for (num <- nums) {
      res += num;
    }
    res
  }
  
}

 

 

4.7   條件表達式

scala條件判斷語句中最後一行可以作爲結果返回

//演示scala條件表達式
object IfScala extends App {

  val x = 0
  val resBoolean = if (x > 0) true else false //false

  // 0 > 0 不成立, 且代碼沒有 else 分支, res2 是什麼呢
  val res2 = if (x > 0) 2 //()   相當於else ()

  // if ... else if ... else 代碼較多時可以使用代碼塊{}
  val score = 78
  val res4 = {
    if (score > 60 && score < 70) "及格"
    else if (score >= 70 && score < 80) "良好" else "優秀"
  }
  println(res4)  //良好
}

 

 

4.8    循環表達式[for/while/yield關鍵字]

scala中的循環表達式分爲3類:① to     ②  Range   ③ until  【底層實際上都是scala.collection.immutable.Range】

可以設置步長【但注意步長不能爲0】

until底層實際上也是調用Range

在IDEA中如何使用呢?

輸出結果爲:   Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

下面將演示for循環、foreach循環、while循環

//循環表達式
object Function5 {
  def main(args: Array[String]): Unit = {}

  //循環演示1 (for)
  // for (i <- 1 to 10 ; if i % 2 == 0)也可以
  for (i <- 1 to 10 if i % 2 == 0) {
    println(i)
  }

  //循環演示2 (for)
  var skills = Array("Hadoop", "Spark", "Storm", "Hive", "HBase", "Kafka")
  for (skill <- skills) {
    println(skill)
  }

  //循環演示3 (foreach)
  //skill實際上是skills裏面的每個元素
  // => 實際上是將左邊的skill作用到右邊的一個函數,輸出另外一個結果
  skills.foreach(skill => println(skill))

  //循環演示4 累加100到0的每個數(while)
  var (sum, num) = (0, 100)
  while (num >= 0) {
    sum += num;
    num = num - 1; //scala不能num --   步長
  }
  println(sum)
}

細心觀察的朋友可能會發現,scala中的for和while都不需要書寫在某個方法體裏面即可執行,這不同於Java。

雙重for循環演示:

//兩種for循環等價

for(i <- 1 to 3 ; j <- 1 to 3 ; if i != j){
    println((10 * i + j) + " ")
  }

  for(i <- 1 to 3){
    for(j <- 1 to 3){
      if(i != j){
        println((10 * i + j) + " ")
      }
    }
  }

yield關鍵字的主要作用就是記住每次迭代中的相關值,並逐一存入到一個數組中

yield關鍵字演示:

object LoopYield extends App {
  var arr = Array(1,2,3,4,5)
  val res = for(e <- arr if e % 2 == 0) yield e   //res爲數組類型
}

比如Java場景中從List過濾出商品價格大於10的商品,那麼先要遍歷List,再判斷,再裝入一個新的List中。 

scala> var arr = Array(1,2,3,4,5)
arr: Array[Int] = Array(1, 2, 3, 4, 5)

scala> val res = for(x <- arr if x % 2 == 0) yield x
res: Array[Int] = Array(2, 4)

 

4.9    運算符/運算符重載

Scala 中的+ - * / %等操作符的作用與 Java 一樣,位操作符 & | ^ >> <<也一樣。只是有一點特別的:這些操作符實際上是方法。例如:
a+b
是如下方法調用的簡寫:
a.+(b)
a 方法 b 可以寫成 a.方法(b)   在Scala中,這叫運算符重載方法

 

4.10    高階函數

//高階函數
object HighFunction extends App {
  //高階函數: 將其他函數作爲參數或其結果是函數的函數

  //定義一個方法,其參數爲含一個整數的參數,返回值爲String的函數f 和 一個整數的參數v ,返回值爲一個函數
  def apply(f: Int => String, v: Int) = f(v)  //"[" + 10.toString + "]"

  //定義一個方法其參數爲含一個整數的參數,返回值爲String
  def layout(x: Int) = "[" + x.toString + "]"   //layout: Int => String

  //調用
  println(apply(layout, 10))
}

 

4.11    部分參數應用函數

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

 

五、Scala面向對象

5.1    類的定義和使用

plus:佔位符_ 的使用【不能使用val

例子演示:

object Test1 {
  def main(args: Array[String]): Unit = {
      //創建Person對象
    val person = new Person()
    println(person.name + " " + person.age) //null 18
    person.name = "小明"
    //person.age = 19 報錯  val age 不可變
    println(person.name + " " + person.age) //小明 18
    println(person.eat)  //調用person的eat()方法   小明is eating
    person.watchMovie("泰坦尼克號")  //小明 is watching 泰坦尼克號
    person.printInfo  //調用person中的printInfo()方法    小明的性別是 male
    // person.gender 報錯  private [this] 所修飾的變量只能在其本類訪問,故編譯不通過s
  }
}

//在scala中定義一個類
class Person{
  //相當於書寫了getter/setter(var)
  var name : String = _   //佔位符_   必須爲var,不能爲val,否則報錯 String佔位符默認爲null
  //相當於書寫了setter(val)
  val age : Int = 18   //也可以寫成 val age = 18

  private [this] val gender = "male"

  def printInfo: Unit ={
    println(name + "的性別是 "+ gender)  //private [this] 可以在本類Person中使用
  }

  //定義方法
  def eat():String = {
    name + "is eating"
  }

  def watchMovie(movieName : String) :Unit = {
    println(name + " is watching " + movieName)
  }

}

 

5.2    構造器【主構造器與附屬構造器】

//scala構造器演示
object Constructor {
  def main(args: Array[String]): Unit = {
    val student = new Student("小明", 20)
    println("姓名:" + student.name + " 年齡:" + student.age + " 職業:" + student.occupation) //姓名:小明 年齡:20 職業:學生

    val student2 = new Student("張三", 21, "男")
    println("姓名:" + student2.name + " 年齡:" + student2.age + " 職業:" + student2.occupation) //姓名:小明 年齡:20 職業:學生
  }
}

//主構造器:緊跟在類後面的參數列表  val或var不可省略,否則第行student.屬性(省略var或val的報錯,無法訪問)
//如果成員屬性var修飾相當於對外提供getter/setter方法  val修飾相當於對外提供getter方法
class Student(val name: String, val age: Int) {
  println("主構造器執行")
  val occupation: String = "學生"
  var gender: String = _

  //附屬構造器
  def this(name: String, age: Int, gender: String) {
    this(name, age) //附屬構造器第一行代碼必須調用主構造器或其他附屬構造器
    this.gender = gender
  }

  println("主構造器結束")
}

運行結果:

主構造器執行
主構造器結束
姓名:小明 年齡:20 職業:學生
主構造器執行
主構造器結束
姓名:張三 年齡:21 職業:學生

 

私有主構造器,設置主構造器的訪問權限【注意private是放在主構造器前面】

class Teacher private(var name: String, age: Int) {}

私有附屬構造器

class Teacher(var name: String, age: Int) {
  var sex: String = _

  //私有輔助構造器
  private def this(name: String, age: Int, sex: String) = {
    //在輔助構造器中必須先調用主構造器
    this(name, age)
    this.sex = sex
  }
}

類的成員屬性訪問權限:如果類的主構造器中成員屬性是private修飾的,它的getter/setter方法都是私有的

class Teacher(private  var name: String, age: Int) {
    //...
}

修飾類的訪問權限,私有類private [this] class XXX

//類的前面加上private 標識這個類在當前包下都見,而且當前包下的子包不可見 默認private[this]
private[this] class Teacher(var name: String, val age: Int){}

//類的前面加上private[包名] 如private[itcats]表示這個類在itcats包及其子包下都可見
private[itcats] class Teacher(var name: String, val age: Int){}

 

 

5.3    繼承與重寫

繼承:

在創建子類對象時候,首先會執行父類的構造方法

object Test2 {
  def main(args: Array[String]): Unit = {
    var an = new Bird("鸚鵡",2,"黑色")
    println(an.name + " "+an.age + " "+ an.color)
  }
}

//父類主構造器
class Animal(val name: String, val age: Int) {
  println("Animal Constructor in")
  println("Animal Constructor out")
}


//子類主構造
//這裏需要注意,從父類繼承過來的name,age,在子類主構造器中不需要寫var/val , 而color沒有繼承過來,必須要寫var/val
class Bird(name: String, age: Int, val color: String) extends Animal(name, age) {
  println("Bird Constructor in")

  println("Bird Constructor out")
}

運行結果:

Animal Constructor in
Animal Constructor out
Bird Constructor in
Bird Constructor out
鸚鵡 2 黑色

 

重寫:

對於父類已有的屬性和方法,子類重寫需要加上override關鍵字

//重寫
object Test3 {
  def main(args: Array[String]): Unit = {
    var apple = new Apple("iPhone Xs",4999.9,"黑色")
    println(apple.toString)
  }
}

//父類主構造器
class Phone(var name: String, val price: Double) {
    val country = "中國"  //注意: 此處必須爲val類型
}


//子類主構造
//這裏需要注意,從父類繼承過來的name,age,在子類主構造器中不需要寫var/val , 而color沒有繼承過來,必須要寫var/val
class Apple(name: String, price: Double, val color: String) extends Phone(name, price) {
  //重寫父類的name屬性
  override val country = "美國"   //val country = "美國"報錯,必須要加override關鍵字,並且必須爲val 類型  否則將會顯示mutable變量不可改變的錯誤

  //重寫toStirng()方法
  override def toString() : String = {
    "name = " + name +" , price = "+ price+" , "+"color = " +color+" , "+"country = " +country
  }
}

輸出結果:

name = iPhone Xs , price = 4999.9 , color = 黑色 , country = 美國

 

5.4    抽象類

抽象類使用abstract關鍵字修飾,且抽象類中[屬性、方法]可以有具體的實現,也可以沒有具體的實現,抽象類不能new

object AbstractTest {
  def main(args: Array[String]): Unit = {
    //抽象類不能被new   new Plane() 報錯
    var boeing = new Boeing();   //可以new實現類 編譯通過
  }
}

/*
 *  類的一個或者多個方法沒有完整的實現(只有定義,沒有實現)
 */
abstract class Plane {
  def speak

  val name: String
  val age: Int
}

//創建Plane的實現類  即可以是class 也可以是object
class Boeing extends Plane {
  override def speak: Unit = ??? // ???等同於 {}
  override val name: String = "737"  //使用IDEA提示生成的 override val name: String = _ 運行報錯 unbound placeholder parameter
  override val age: Int = 2         //override val age: Int = _ 報錯unbound placeholder parameter
}

關於抽象類與Trait的順序問題

//先extends 後 with 
class AbsClassImpl extends AbsClass with Trait1{}   //這種是可以的,先繼承抽象類,後with特質

class AbsClassImpl extends Trait1 with Abstract{}   //這種是禁止的~~~

//在Scala中第一個繼承抽象類或特質,只能先使用extends關鍵字 
//如果想繼承多個特質,在extends後面再使用with關鍵字

 

5.5    伴生類與伴生對象

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

Scala中的object對象也會被翻譯爲class字節碼文件,但沒辦法new。

object中定義的成員變量和方法都是靜態的

Scala 中使用單例模式時,除了定義的類之外,還要定義一個同名的 object 對象,它和類的區別是,object對象不能帶參數。

類和它的伴生對象可以互相訪問其私有成員方法和屬性

伴生對象可以訪問類中的私有方法private,不能訪問private[this]修飾的成員變量即方法

object ApplyApp {
}

//伴生類與伴生對象
class ApplyTest{ 
}

object ApplyTest{
}

如果有一個class,還有一個與之同名的object,那麼就稱這個object是是class的伴生對象,class是object的伴生類

其中class ApplyTest是object ApplyTest的伴生類,object ApplyTest是class ApplyTest的伴生對象。

 

 

5.6    apply

object ApplyApp {
  def main(args: Array[String]): Unit = {
    for (i <- 1 to 10) { //循環10次
      ApplyTest.incr
    }
    println(ApplyTest.count) //10 object本身就是一個單例對象
  }
}

//伴生類與伴生對象
class ApplyTest {}

object ApplyTest {
  println("object ApplyTest start")
  var count = 0

  def incr: Unit = {
    count = count + 1
  }

  println("object ApplyTest end")
}

輸出結果

object ApplyTest start
object ApplyTest end
10

 

面試高頻考點:

object ApplyApp {
  def main(args: Array[String]): Unit = {
    //3.
    val b = ApplyTest()   //==>調用了object.apply()
    //4.
    println("------------分割線------------")
    var c = new ApplyTest
    println(c)
    c()                   //==>調用了class.apply()

    /*總結:
     *        類名()  ==>   object.apply()
     *        對象()  ==>   class.apply()
     *        通常做法是在object中的apply()中最後一行new class
     */
  }
}

//伴生類與伴生對象
class ApplyTest {
  def apply(): Unit ={
    println("class ApplyTest apply")
  }
}

object ApplyTest {
  println("object ApplyTest start")

  //1.在object中書寫一個apply()方法 注意此處apply()括號不可省略,否則ApplyTest()報錯
  def apply(): ApplyTest ={
    println("object ApplyTest apply")

  //2.在object中的apply()方法內new class
    new ApplyTest
  }

  println("object ApplyTest end")
}

 

 

5.7    case class樣例類和case object樣例對象

//支持模式匹配
//樣例類和樣例對象默認實現了Serializable接口、Product接口
//通常用在模式匹配
object CaseClass {
  def main(args: Array[String]): Unit = {
    println(Language("English").name)
  }
}

//特點case class不用new即可使用
case class Language(name : String){    //case class className {}沒有參數是不允許的,但可以使用case class className() {}

}

//樣例對象  樣例類不能有屬性
case object CaseObject

 

5.8    Trait

Scala Trait(特徵) 相當於 Java 的接口,實際上它比接口還功能強大。

與接口不同的是,它還可以定義屬性和方法的實現。

一般情況下Scala的類只能夠繼承單一父類,但是如果是 Trait(特徵) 的話就可以繼承多個,從結果來看就是實現了多重繼承。

如果特質中trait某個方法有具體的實現,在子類繼承重寫的時候必須使用override關鍵字

如果特質方法沒有具體的實現,子類在實現的時候可以不加override關鍵字也可以加

Trait(特徵) 定義的方式與類類似,但它使用的關鍵字是 trait,如下所示:

trait Equal {
  def isEqual(x: Any): Boolean
  def isNotEqual(x: Any): Boolean = !isEqual(x)
}

以上Trait(特徵)由兩個方法組成:isEqual 和 isNotEqual。isEqual 方法沒有定義方法的實現,isNotEqual定義了方法的實現。子類繼承特徵可以實現未被實現的方法。

一個類可以擴展多個特質使用with進行連接,如

trait xxx extends ATrait with BbTrait
object Person extends App{
  //ScalaTrait爲自定義的特質類
  val student = new Student with ScalaTrait
  //此時student對象就具有ScalaTrait的所有方法或屬性
  //在Scala中可以動態的混入特質
  //可以with多個Trait 
  val student = new Student with ScalaTrait with STrait  
}

 //可以在定義的時候混入特質,也可以在創建對象的時候混入特質
object ScalaTraitImpl extends ScalaTrait with STrait{}

總結:

1、類與對象都可以混入一個或多個特質

2、特質中可以定義有具體實現的方法和沒有具體實現的方法

3、如果一個類或者一個對象(繼承)混入了某一個特質,這個特質中有一個未被實現的方法A和一個已實現的方法B

這個類或對象必須實現這個沒有實現的方法A,且可選擇性的重寫方法B,必須使用override關鍵字。

 

提問:Scala中特質trait與抽象類abstract的區別?

  • 優先使用特質trait。一個類擴展多個特質trait是很方便的,但卻只能擴展一個抽象類。
  • 如果你需要構造函數參數,使用抽象類。因爲抽象類可以定義帶參數的構造函數,而特質不行。例如:
  • trait t(i: Int) {},參數i是非法的。

 

5.9    final關鍵字

與Java一致。

在父類中使用final關鍵字修飾的類,子類不能繼承

在父類中使用final關鍵字修飾的變量,不能被修改/重新賦值

在父類中使用final關鍵字修飾的方法,子類不能重寫

需要注意的一點是,final可以修飾abstract類,但是這麼做沒有意義。

 

5.10    type關鍵字

type的作用類似於別名

scala> type S = String
defined type alias S

scala> val str: S = "hello"
str: S = hello

Scala代碼示例: 

trait TypeDefined {
  //約定一個type變量而不實現
  type T
  
  def myPrint(x: T): Unit = {
    println(x)
  }
}
object TypeDefindImpl1 extends App with TypeDefined {
  override type T = String
  //可見,參數類型爲String類型,特質種的 type t 起到的是一個約定作用
  override def myPrint(x: String): Unit = {
    println(x)
  }
  TypeDefindImpl1.myPrint("haha")
}

 

六    Scala集合

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

可變集合是指:長度可變,內容可變

不可變集合是指:長度不可變,內容不可變

 

6.1    數組

不可變長數組[scala.collection.immutable]  plus:Array是內容可變,但長度不可變的數組

object ArrayApp extends App {

  //創建定長數組
  //方式1 使用new Array[類型](數組長度方式)
  val array1 = new Array[String](3);
  array1.length //長度爲3
  array1(1) //獲取數組中索引爲1的元素
  array1(1) = "haha" //將數組中索引爲1的元素改爲haha


  //方式2:使用Array object中的apply(),底層幫我們new了Array,返回值類型爲Array[T]
  val b = Array("hadoop", "spark", "storm", "flink")

  val c = Array(1,2,3,4,5)
  c.sum
  c.min
  c.max
  //將數組轉換爲一個字符串
  c.mkString      //12345
  c.mkString(",") // 1,2,3,4,5
  c.mkString("(",",",")")  // (1,2,3,4,5)
}

可變長數組[scala.collection.mutable]  ArrayBuffer是內容可變,而且長度可變的數組

/**
  * 可變長度的數組 ArrayBuffer
  */
object ArrayBufferApp {
  def main(args: Array[String]): Unit = {

    var c = scala.collection.mutable.ArrayBuffer[Int](1,2,3) //創建一個可變長度的數組ArrayBuffer
    c += 4                //ArrayBuffer(1, 2, 3, 4)
    c += (5,6,7)          //ArrayBuffer(1, 2, 3, 4, 5, 6, 7)
    c ++= Array(9,10)     //ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 9, 10)
    c.insert(0,0)   //def insert(index: Int ,elems: A*) 向index位置添加elems(可變)  ArrayBuffer(0, 1, 2, 3, 4, 5, 6, 7, 9, 10)
    c.remove(0,1)  //def remove(n: Int,count: Int) 從索引爲n開始,向後刪除count個元素  ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 9, 10)
    c.trimEnd(4)   //從尾巴開始刪除4個元素    ArrayBuffer(1, 2, 3, 4, 5)
    c.toArray     //將可變數組ArrayBuffer轉換爲不可變數組Array
    c.remove(1)   //ArrayBuffer(1, 3, 4, 5)
    //遍歷方式1
    for(ele <- c){
      println(ele)
    }

    //遍歷方式2,使用to Range Until
    for(i <- 0 to c.length - 1) {  //length = 4  c(0)、c(1)、c(2)、c(3)
      print(c(i))
    }


    for(i <- Range(0,c.length)){   //i 0~3
      print(c(i))
    }

    for(i <- 0.until(c.length)){   //i 0~3
      print(c(i))
    }

    //如何將數組倒序輸出呢?
    for(i <- (0 until c.length).reverse){  //實際上反轉了i的輸出順序
      print(c(i))
    }
  }
}

 

6.2    List【有序可重複有索引的集合】

內容不可變長度不可變的List【注意var和val】

scala> var list = List(1,2,3)
list: List[Int] = List(1, 2, 3)

scala> list ++=List(4,5)  //var指內存地址可變,生成新的List,list指向新的內存地址

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

scala> val list1 = List(1,2,3)
list1: List[Int] = List(1, 2, 3)

scala> list1 ++= List(4,5)
<console>:13: error: value ++= is not a member of List[Int]
       list1 ++= List(4,5)

 

Nil就是一個空的不可變的(immutable)的List

可見:List第一個元素爲head,後面的元素都爲tail

頭 :: 尾 ,所以最後一條命令 4是head ,List(1,2,3)爲tail。

scala> val list1 = List(1,2,3)
list1: List[Int] = List(1, 2, 3)

scala> (-1)::(0)::list1
res24: List[Int] = List(-1, 0, 1, 2, 3)

scala> list::List(5,6)
res25: List[Any] = List(List(1, 2, 3), 5, 6)

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

 

 內容可變長度可變的ListBuffer

object ListApp extends App {
  val l = List(1,2,3,4,5) //底層是object List的apply()

  //定義可變的List
  val l1 = scala.collection.mutable.ListBuffer[Int]()   //底層是apply() 所以()括號不能省略
  l1 += 1             //ListBuffer(1)
  l1 += (2,3,4)       //ListBuffer(1, 2, 3, 4)
  l1 ++= List(5,6)    //ListBuffer(1, 2, 3, 4, 5, 6)  ++= 相加的是List集合
  l1 ++:(List(999))   //List[Int] = List(1, 2, 3, 4, 5, 6, 999)
  l1.++: (List(0))   //ListBuffer(0, 1, 2, 3, 4, 5, 6)


  //將0插入list1的前面生成新的list
  val list1 = List(1,2,3)
  val r1 = 0 :: list1
  val r2 = list1.::(0)
  val r3 = 0 +: list1
  val r4 = list1.+:(0)

 //在list1後面插入0
 val r5 = list.:+(0)

  //減少元素
  l1 -= 3             //減去元素3                   ListBuffer(1, 2, 4, 5, 6)
  l1 -= (0,4,6)       //沒有0但不報錯,沒有就不減去0唄  ListBuffer(1, 2, 5)
  l1 --= List(1,5)    //ListBuffer(2)

  l1.toList           //轉換爲List(不可變長)
  l1.toArray          //轉換爲Array
  l1.isEmpty
  l1.head
  l1.tail
  l1.mkString("|")  //使用|分割List中的每個元素,返回String
  println(l1)
  l1.count(x => x > 2) //l1.count(f: Int => Boolean) : Int  計算l1中大於2的值有多少個
  l1.filter(x => x > 2) //返回值爲List[Int] 返回集合中符合x > 2的元素集合
  l1.slice(1,3)        //從Index 1開始截取 到 Index 2(不包括3 方法簽名是until)
  l1.sum


  //遞歸求和算法
  def sum(nums : Int* ):Int = {
    if(nums.length == 0){
      0
    }
    else {
      nums.head + sum(nums.tail:_*)      
    }
  }
}

排序:

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

scala> words.sortBy(x => x._2)
res29: List[(String, Int)] = List((c,1), (b,2), (a,3))

scala> words.sortBy(x => - x._2)
res30: List[(String, Int)] = List((a,3), (b,2), (c,1))

分組

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

scala> list.grouped(2).toList  //如果不toList的話是一個Iterator
res1: List[List[Int]] = List(List(1, 2), List(3, 4))

 

6.3    Set【無序不可重複無索引的集合】

可變Set   collection.mutable.HashSet

 

scala> val hset = collection.mutable.HashSet(1,3,4)
hset: scala.collection.mutable.HashSet[Int] = Set(1, 3, 4)

scala> val set = Set(1,2,3,3)
set: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

scala> hset.add(5)
res26: Boolean = true

scala> hset
res27: scala.collection.mutable.HashSet[Int] = Set(1, 5, 3, 4)

scala> hset.add(1)
res28: Boolean = false

scala> hset.remove(1)
res29: Boolean = true

scala> hset.remove(999)
res30: Boolean = false

scala> hset.-=(3)
res31: hset.type = Set(5, 4)

scala> hset ++ Set(0,9)  //++ 原集合數值無變化
res33: scala.collection.mutable.HashSet[Int] = Set(0, 9, 5, 4)

scala> hset ++= Set(0,9)  //++=原集合數據發生變化
res34: hset.type = Set(0, 9, 5, 4)

不可變Set 

scala> val set = collection.mutable.HashSet(1,3,4)
set: scala.collection.mutable.HashSet[Int] = Set(1, 3, 4)

 

6.4    Map

Map(映射)是一種可迭代的鍵值對(key/value)結構。

所有的值都可以通過鍵來獲取。

Map 中的鍵都是唯一的。

Map 也叫哈希表(Hash tables)。

Map 有兩種類型,可變與不可變,區別在於可變對象可以修改它,而不可變對象不可以。

默認情況下 Scala 使用不可變 Map。如果你需要使用可變集合,你需要顯式的引入 import scala.collection.mutable.Map 類

在 Scala 中 你可以同時使用可變與不可變 Map,不可變的直接使用 Map,可變的使用 mutable.Map。以下實例演示了不可變 Map 的應用

// 空哈希表,鍵爲字符串,值爲整型
var A:Map[Char,Int] = Map()

// Map 鍵值對演示
val colors = Map("red" -> "#FF0000", "azure" -> "#F0FFFF")

// Map鍵值對演示2
val colors = Map[String,String]("red" -> "#FF0000", "azure" -> "#F0FFFF") 

 內容不可變,長度不可變Map

scala> val map = Map[String,String]("red" -> "#FF0000", "azure" -> "#F0FFFF")
map: scala.collection.immutable.Map[String,String] = Map(red -> #FF0000, azure -> #F0FFFF)

scala> map("red") = "xxx"
<console>:13: error: value update is not a member of scala.collection.immutable.Map[String,String]
       map("red") = "xxx"

內容可變,長度可變的scala.collection.mutable.HashMap

scala> val hmap = scala.collection.mutable.HashMap[String,Int]()
hmap: scala.collection.mutable.HashMap[String,Int] = Map()

scala> hmap.+=("a" -> 1)
res2: hmap.type = Map(a -> 1)

添加元素

定義 Map 時,需要爲鍵值對定義類型。如果需要添加 key-value 對,可以使用 + 號,如下所示:

A += ('I' -> 1)
A += ('J' -> 5)
A += ('K' -> 10)
A += ('L' -> 100)
scala> hmap.put
   override def put(key: String,value: Int): Option[Int]

scala> hmap.put("bb",8)
res3: Option[Int] = None

scala>  hmap += (("xx",66))  //添加對偶元組
res9: hmap.type = Map(bb -> 8, a -> 1, age -> 100, name -> 100, xx -> 66)

刪除元素

scala> hmap
res10: scala.collection.mutable.HashMap[String,Int] = Map(bb -> 8, a -> 1, age -> 100, name -> 100, xx -> 66)

scala> hmap.remove("aa")
res11: Option[Int] = None

scala> hmap.remove("a")
res12: Option[Int] = Some(1)

scala> hmap
res13: scala.collection.mutable.HashMap[String,Int] = Map(bb -> 8, age -> 100, name -> 100, xx -> 66)

scala> hmap.-=("bb")
res14: hmap.type = Map(age -> 100, name -> 100, xx -> 66)

獲取元素

scala> hmap.-=("bb")
res14: hmap.type = Map(age -> 100, name -> 100, xx -> 66)

scala> hmap.get("age")
res16: Option[Int] = Some(100)

scala> hmap.get("age").get
res17: Int = 100

scala> hmap.get("bbb")   //不存在bbb這個key 返回就是None對象
res18: Option[Int] = None   

scala> hmap.get("bbb").get
java.util.NoSuchElementException: None.get
  at scala.None$.get(Option.scala:347)
  at scala.None$.get(Option.scala:345)
  ... 32 elided

scala> hmap.getOrElse("a",0)
res21: Int = 0

scala> hmap.getOrElse("age",0)
res22: Int = 100

 

 

Scala Map 有三個基本操作:

方法 描述
keys 返回 Map 所有的鍵(key)          Set(key1,key2...)
values 返回 Map 所有的值(value)       MapLike(v1,v2...)
isEmpty 在 Map 爲空時返回true            

Map 合併:

object Test {
   def main(args: Array[String]) {
      val colors1 = Map("red" -> "#FF0000",
                        "azure" -> "#F0FFFF",
                        "peru" -> "#CD853F")
      val colors2 = Map("blue" -> "#0033FF",
                        "yellow" -> "#FFFF00",
                        "red" -> "#FF0000")

      //  ++ 作爲運算符
      var colors = colors1 ++ colors2
      println( "colors1 ++ colors2 : " + colors )

      //  ++ 作爲方法
      colors = colors1.++(colors2)
      println( "colors1.++(colors2)) : " + colors )

   }
}

執行以上代碼,輸出結果爲:

$ scalac Test.scala 
$ scala Test
colors1 ++ colors2 : Map(blue -> #0033FF, azure -> #F0FFFF, peru -> #CD853F, yellow -> #FFFF00, red -> #FF0000)
colors1.++(colors2)) : Map(blue -> #0033FF, azure -> #F0FFFF, peru -> #CD853F, yellow -> #FFFF00, red -> #FF0000)

輸出 Map 的 keys 和 values

object Test {
   def main(args: Array[String]) {
      val sites = Map("runoob" -> "http://www.runoob.com",
                       "baidu" -> "http://www.baidu.com",
                       "taobao" -> "http://www.taobao.com")

      sites.keys.foreach{ i =>  
                           print( "Key = " + i )
                           println(" Value = " + sites(i) )}
   }
}

執行以上代碼,輸出結果爲:

$ scalac Test.scala 
$ scala Test
Key = runoob Value = http://www.runoob.com
Key = baidu Value = http://www.baidu.com
Key = taobao Value = http://www.taobao.com

 

6.5    Option&Some&None

Scala 程序使用 Option 非常頻繁,在 Java 中使用 null 來表示空值,代碼中很多地方都要添加 null 關鍵字檢測,不然很容易出現 NullPointException。因此 Java 程序需要關心那些變量可能是 null,而這些變量出現 null 的可能性很低,但一但出現,很難查出爲什麼出現 NullPointerException。

Scala 的 Option 類型可以避免這種情況,因此 Scala 應用推薦使用 Option 類型來代表一些可選值。使用 Option 類型,讀者一眼就可以看出這種類型的值可能爲 None。

 

Scala Option(選項)類型用來表示一個值是可選的(有值或無值)。

Option[T] 是一個類型爲 T 的可選值的容器: 如果值存在, Option[T] 就是一個 Some[T] ,如果不存在, Option[T] 就是對象 None

// 雖然 Scala 可以不定義變量的類型,不過爲了清楚些,我還是
// 把他顯示的定義上了
 
val myMap: Map[String, String] = Map("key1" -> "value")
val value1: Option[String] = myMap.get("key1")
val value2: Option[String] = myMap.get("key2")
 
println(value1) // Some("value1")
println(value2) // None

//println(value2.get)  //拋出異常 java.util.NoSuchElementException: None.get
  println(value1.get)    //value

在上面的代碼中,myMap 是一個 Key 的類型是 String,Value 的類型是 String 的 hash map,但不一樣的是他的 get() 返回的是一個叫 Option[String] 的類別。

Scala 使用 Option[String] 來告訴你:「我會想辦法回傳一個 String,但也可能沒有 String 給你」。

myMap 裏並沒有 key2 這筆數據,get() 方法返回 None。

Option 有兩個子類別,一個是 Some,一個是 None,當他回傳 Some 的時候,代表這個函式成功地給了你一個 String,而你可以透過 get() 這個函式拿到那個 String,如果他返回的是 None,則代表沒有字符串可以給你。

 

getOrElse() 方法【沒有值就給默認值,避免拋出異常】

你可以使用 getOrElse() 方法來獲取元組中存在的元素或者使用其默認的值,實例如下:

object Test {
   def main(args: Array[String]) {
      val a:Option[Int] = Some(5)
      val b:Option[Int] = None 
      
      println("a.getOrElse(0): " + a.getOrElse(0) )
      println("b.getOrElse(10): " + b.getOrElse(10) )
   }
}

執行以上代碼,輸出結果爲:

$ scalac Test.scala 
$ scala Test
a.getOrElse(0): 5
b.getOrElse(10): 10

isEmpty() 方法

你可以使用 isEmpty() 方法來檢測元組中的元素是否爲 None,實例如下:

object Test {
   def main(args: Array[String]) {
      val a:Option[Int] = Some(5)
      val b:Option[Int] = None 
      
      println("a.isEmpty: " + a.isEmpty )
      println("b.isEmpty: " + b.isEmpty )
   }
}

執行以上代碼,輸出結果爲:

$ scalac Test.scala 
$ scala Test
a.isEmpty: false
b.isEmpty: true

 

6.6    Tuple(Scala 元組)

與列表一樣,元組也是不可變的,但與列表不同的是元組可以包含不同類型的元素。

元組的值是通過將單個的值包含在圓括號中構成的。例如:

val t = (1, 3.14, "Fred")  

以上實例在元組中定義了三個元素,對應的類型分別爲[Int, Double, java.lang.String]。

此外我們也可以使用以下方式來定義:

val t = new Tuple3(1, 3.14, "Fred")

元組的實際類型取決於它的元素的類型,比如 (99, "runoob") 是 Tuple2[Int, String]。 ('u', 'r', "the", 1, 4, "me") 爲 Tuple6[Char, Char, String, Int, Int, String]。

目前 Scala 支持的元組最大長度爲 22。對於更大長度你可以使用集合,或者擴展元組。

訪問元組的元素可以通過數字索引,如下一個元組:

val t = (4,3,2,1)

我們可以使用 t._1 訪問第一個元素, t._2 訪問第二個元素,如下所示:

object Test {
   def main(args: Array[String]) {
      val t = (4,3,2,1)

      val sum = t._1 + t._2 + t._3 + t._4

      println( "元素之和爲: "  + sum )
   }
}
$ scalac Test.scala 
$ scala Test
元素之和爲: 10

迭代元組

你可以使用 Tuple.productIterator() 方法來迭代輸出元組的所有元素:

object Test {
   def main(args: Array[String]) {
      val t = (4,3,2,1)
      
      t.productIterator.foreach{ i =>println("Value = " + i )}
   }
}
$ scalac Test.scala 
$ scala Test
Value = 4
Value = 3
Value = 2
Value = 1
scala> val tuple = (1,true,"xx",Unit)
tuple: (Int, Boolean, String, Unit.type) = (1,true,xx,object scala.Unit)

scala> tuple.productIterator.toList
res24: List[Any] = List(1, true, xx, object scala.Unit)

scala> tuple.productIterator.foreach(println)
1
true
xx
object scala.Unit

 

元組轉爲字符串

你可以使用 Tuple.toString() 方法將元組的所有元素組合成一個字符串,實例如下:

object Test {
   def main(args: Array[String]) {
      val t = new Tuple3(1, "hello", Console)
      
      println("連接後的字符串爲: " + t.toString() )
   }
}

執行以上代碼,輸出結果爲:

$ scalac Test.scala 
$ scala Test
連接後的字符串爲: (1,hello,scala.Console$@4dd8dc3)

元素交換【只適用於對偶元組】

你可以使用 Tuple.swap 方法來交換元組的元素。如下實例:

object Test {
   def main(args: Array[String]) {
      val t = new Tuple2("www.google.com", "www.runoob.com")
      
      println("交換後的元組: " + t.swap )
   }
}

執行以上代碼,輸出結果爲:

$ scalac Test.scala 
$ scala Test
交換後的元組: (www.runoob.com,www.google.com)

 

6.7    Seq序列

不可變的序列 import scala.collection.immutable._
在 scala 中列表要麼爲空(Nil 表示空列表)要麼是一個 head 元素加上一個 tail 列表。9 :: List(5, 2) :: 操作符是將給定的頭和尾創建一個新的列表

scala> Nil
res0: scala.collection.immutable.Nil.type = List()

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

scala> list.head
res1: Int = 1

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

scala> 100::Nil
res3: List[Int] = List(100)

scala> var list = Nil
list: scala.collection.immutable.Nil.type = List()

scala> var list1 = List(1)
list1: List[Int] = List(1)

scala> list1.::(list)
res5: List[Any] = List(List(), 1)

scala> 1::2::3::Nil
res6: List[Int] = List(1, 2, 3)

 

6.8    並行化集合

val list = List(1,2,3,4,5)
list.par.fold(0)(_+_)
list.par.foldLeft(0)(_+_)
list.par.aggregate()

 

七    Scala模式匹配

7.1    基本數據類型的模式匹配

object BaseMatch {
  val a: Array[String] = Array("a,", "b", "c", "d")
  val name: String = a(Random.nextInt(a.length))
  name match {
    case "a" => println("This is a")
    case "b" => println("This is b")
    case "c" => println("This is c")
    case "d" => println("This is d")
    case _   => println("Can not find it")
  }

  def matchMethod(num : Int) :String = num match {
    case 1 => "This is a"
    case 2 => "This is b"
    case 3 => "This is c"
    case 4 => "This is d"
    case 4 => "This is e"
    case _ => "Can not fiSnd it"
  }

  def main(str: Array[String]): Unit = {
     //一旦匹配上就直接退出模式匹配
     matchMethod(4)  //返回是"This is d"
  }
}

 

7.1.1    條件過濾

object BaseMatch extends App {
  print(matchMethod("haha",4))   //This is d
  def matchMethod(keyword: String, num: Int): String = num match {
    case 1 => "This is a"
    case 2 => "This is b"
    case 3 => "This is c"
    case 4 if (keyword == "haha") => "This is d"
    case _ => "Can not fiSnd it"
  }
}

 

7.2    Array模式匹配

def who(arr : Array[String]): Unit = arr match {
    //只匹配數組中含有一個"zhangsan"的case
    case Array("zhangsan") => println("This is zhangsan")
    case Array("zhangsan","lisi") => println("This is zhangsan and lisi")
    //匹配數組中只含三個元素的case(對元素沒有要求)
    case Array(x,y,z) => println("This is x,y,z")
    //匹配第一個爲wangwu,後可有可無任意多個元素的case
    case Array("wangwu",_*) => println("This is wangwu and others")
    case _ => println("who is it?")
  }

 

7.3    List模式匹配

與Array的模式匹配十分相似

def whoList(list : List[String]): Unit = list match {
    //只匹配數組中含有一個"zhangsan"的case
    case "zhangsan"::Nil => println("This is zhangsan")
    case "zhangsan"::"lisi"::Nil => println("This is zhangsan and lisi")
    //匹配數組中只含三個元素的case(對元素沒有要求)
    case x::y::z::Nil => println("This is x,y,z")
    //匹配第一個爲wangwu,後可有可無任意多個元素的case
    case "wangwu"::tail => println("This is wangwu and others")
    case m :: n => println("匹配擁有head,tail的List集合")
    case _ => println("who is it?")
  }

 

7.4    Scala異常處理【catch中類型模式匹配】

//Scala中的異常處理
object MyException extends App {
  try{
    val i = 1 / 0; //製造異常
  }catch {
    case e:ArithmeticException => println("除數不能爲0")
    case e:Exception => println("出現了未知異常")
  }finally {
    println("關閉資源處理")
  }
}

 

7.5    case class模式匹配

object CaseClassMatch extends App {

  def caseMatch(plane : Plane): Unit = plane match {
    case Boeing(name: String, age: Int) => println("This is boeing 787 , age is 2")
    case Airbus(name: String, age: Double) => println("This is airbus 330 , age is 0.8")
    case Other(name: String, age: Int) => println("This is 戰鬥機 , age is 7")
  }

  caseMatch(Boeing("播音787",2))
  caseMatch(Airbus("空客330",0.8))
  caseMatch(Other("戰鬥機",7))
}

class Plane

case class Boeing(name: String, age: Int) extends Plane {}

case class Airbus(name: String, age: Double) extends Plane {}

case class Other(name: String, age: Int) extends Plane {}

 

7.6    Some&None模式匹配

操作map中的數據,需要頻繁地調用get方法

object TestOption {
  def main(args: Array[String]): Unit = {
    val bigDataSkills =
      Map("Java" -> "first",
        "Hadoop" -> "second",
        "Spark" -> "third",
        "storm" -> "forth",
        "hbase" -> "fifth",
        "hive" -> "sixth",
        "photoshop" -> null)
 
    println(bigDataSkills.get("Java") == Some("first"))
    println(bigDataSkills.get("photoshop").get == null)
    println(bigDataSkills.get("Spark").get == "third")
    println(bigDataSkills.get("abc123") == None)
 
  }
}

2、通過模式匹配操作map中的數據,這是一個非常函數化的概念,它允許有效地 “啓用” 類型和/或值,更不用說在定義中將值綁定到變量、在 Some() 和 None 之間切換,以及提取 Some 的值(而不需要調用麻煩的 get() 方法)。

object TestOption2 {
 
  def main(args: Array[String]): Unit = {
    val bigDataSkills =
      Map("Java" -> "first",
        "Hadoop" -> "second",
        "Spark" -> "third",
        "storm" -> "forth",
        "hbase" -> "fifth",
        "hive" -> "sixth",
        "photoshop" -> null)
 
    def show(value: Option[String]) =
      {
        value match {
          case Some(a) => a
          case None    => "No this Skill"
        }
      }
 
    println(show(bigDataSkills.get("Java")) == "first")
    println(show(bigDataSkills.get("photoshop")) == null)
    println(show(bigDataSkills.get("Spark")) == "third")
    println(show(bigDataSkills.get("abc123"))== "No this Skill")
 
  }
}

 

7.7    數據類型模式匹配

 //類型模式匹配
  matchType(Map("haha" -> "hehe"))
  def matchType(obj : Any): Unit = {
    obj match{
      case x:Int => println("Int")
      case y:String => println("String")
      case z:Map[_,_] => z.foreach(println)
      case _ => println("unkown")
    }
  }

 

7.8    對象匹配

//匹配對象
  def objectCaseMatch(obj: Any) = obj match {
    case SendHeartBeat(x,y) => println(s"$x $y")   //只能匹配樣例類
    case CheckTimeOutWorker => println("This is CheckTimeOutWorker") //可以匹配對象或樣例對象
    case "registerWorker" => println("registerWorker") 
  }

case class SendHeartBeat(id: String, time: Long)
case object CheckTimeOutWorker

 

 

八    Scala函數的高級操作

8.1    字符串的高級操作

8.1.1    插值和多行

object StringApi extends App {
  val name = "itcats_cn"
  val job = "student"
  val age = 999.999d

  //普通輸出 加了, 會自動產生一個()
  println("name=" + name, "job=" + job)  //輸出結果 (name=itcats_cn,job=student)

  // 文字'f'插值器允許創建一個格式化的字符串,類似於 C 語言中的 printf。
  // 在使用'f'插值器時,所有變量引用都應該是 printf 樣式格式說明符,如%d,%i,%f 等。
  println(f"姓名: $name%s 年齡: $age%1.2f 職業: $job") // 該行輸出有換行
  printf("姓名: %s 年齡: %1.2f 職業: %s", name, age, job) // 該行輸出沒有換行

  // 's'允許在處理字符串時直接使用變量。
  // 在println語句中將String變量($name)附加到普通字符串中。
  println()
  println(s"姓名: ${name} 年齡: $age 職業: $job")   //可以{}也可以不{}

  // 字符串插入器還可以處理任意表達式。
  // 使用's'字符串插入器處理具有任意表達式(${1 + 1})的字符串(1 + 1)的以下代碼片段。 任何表達式都可以嵌入到${}中。
  println(s"1 + 1 = ${1 + 1}")  // 1 + 1 = 2

  //多行字符串演示(連續輸入三個"後按回車)
  val c =
    """
      |Hello
      |World
    """.stripMargin
  println(c)
}

 

 

8.2    匿名函數

//匿名函數:函數是可以命名也可以匿名的
object AnonymityApp extends App {
  //命名函數定義
  def hello(name: String): Unit = {
    println(s"Hello : $name")
  }

  //匿名函數可以傳遞給變量
  val f = (name:String) => println(s"Hello : $name")
  //也可以傳遞給函數
  def an = (name:String) => println(s"Hello : $name")

  //調用
  hello("a")
  f("b")
  an("c")
}

 

 

8.3    curry函數

object CurryApp extends App {
  //curry函數:將原來接收兩個參數的一個函數,轉換成2個
  //原函數寫法
  def sum(a: Double, b: Double): Double = {
    a + b
  }

  //curry函數
  def currySum(a: Double)(b: Double): Double = {
    a + b
  }

  //柯里化的演變過程
  def add(x: Int) = (y: Int) => x + y //(y: Int) => x + y 爲一個匿名函數, 
  //也就意味着 add 方法的返回值爲一個匿名函數
  //高階函數,方法的參數是函數或方法的返回值是函數的函數

  //調用
  println(   sum(1,1)         )
  println(   currySum(1)(1)   )
}

 

8.4    高階函數【重點掌握】

object HighLevelFunction extends App {
  val l = List(1, 2, 3, 4, 5)

  //1.map 逐個操作集合裏面的每個元素 實際上是建立一種映射關係,返回的是一個新的數組
  l.map((x: Int) => x + 1) //將List內的每個元素(x:Int) + 1
  l.map(x => x + 1)        //scala能幫我們推斷出List內每個元素是Int,且只有一個元素時候,括號可以省略
  l.map(_ + 1)             //對List中的任意元素都 + 1
  //遍歷
  l.map(_ + 1).foreach(print)    //23456 List[Int]

  println()
  println("-------------")

  //2.在map取出每個元素 + 1的基礎上,使用filter過濾出 < 4的數字
  l.map(_ + 1).filter(_ < 4).foreach(print)   //23  List[Int]

  println()
  println("-------------")

  //3.使用take取集合中的前3個元素
  l.take(3).foreach(print)      //123    List[Int]

  println()
  println("-------------")

  //4.reduce
  print(  l.reduce(_ + _)  )  //15     Int  將l集合中的前後元素兩兩相加 1+2 3+3 6+4 10+5

  println()
  println("-------------")

  //5.reduceLeft = reduce
  print(  l.reduceLeft(_ - _)  )   //-13  1 - 2 - 3 - 4 - 5 = -13

  println()
  println("-------------")

  //6.reduceRight
  print(  l.reduceRight(_ - _)  )  //3   ( 1 - ( 2 - ( 3 - ( 4 - 5 ))))

  println()
  println("-------------")

  //7.fold(第一個括號代表初始值)
  print(  l.fold(0)(_ - _)  )    //-15  0-1-2-3-4-5 = -15

  println()
  println("-------------")

  //8.foldLeft = fold  (0在最左邊)
  print(  l.foldLeft(0)(_ - _))  //-15  0-1-2-3-4-5 = -15

  println()
  println("-------------")

  //9.foldRight        (0在最右邊)
  print(  l.foldRight(0)(_ - _) ) //3    ( 1 - ( 2 - ( 3 - ( 4 - (5 - 0) ) ) ) )

  println()
  println("-------------")

  //10.flatten
  val f = List(List(1,2),List(3,4),List(5,6)) //List內部是一個List類型的元祖
  print(f.flatten)   //flatten可以理解爲壓扁  List(1, 2, 3, 4, 5, 6)

  //11.flatMap(可以理解爲map + flatten)
  println()
  println("-------------")
  //先來看一個例子,將f中List元祖都擴大兩倍(使用map) 第一次使用map取出來的是List(x,y) 第二次用map取出來的是(x,y)
  print(f.map(_.map(_ * 2)))   //List(List(2, 4), List(6, 8), List(10, 12))

  println()
  println("-------------")

  //flatMap所實現的效果是  List(2, 4, 6, 8, 10, 12)
  print(f.flatMap(_.map(_ * 2)))

  println()
  println("-------------")

  //12.flatMap的一些應用
  val t = List("hello,hello,world,hello")
  // t.flatMap(_.split(","))   List(hello, hello, world, hello)
  t.flatMap(_.split(",")).map(x => (x,1)).foreach(println)
  /**
    * 輸出:
    *   (hello,1)
        (hello,1)
        (world,1)
        (hello,1)
    */
}

並行聚合

def aggregate[B](z: =>B)(seqop: (B, A) => B, combop: (B, B) => B): B = foldLeft(z)(seqop)
scala> list.aggregate
   def aggregate[B](z: => B)(seqop: (B, Int) => B,combop: (B, B) => B): B

scala> list.aggregate(0)(_ + _ , _ + _)
res6: Int = 10

並集union  【返回一個新的集合】

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

scala> val list1 = List(1,6)
list1: List[Int] = List(1, 6)

scala> list.union(list1)
res20: List[Int] = List(1, 2, 3, 4, 1, 6)

交集intersect

scala> list.intersect(list1)
res21: List[Int] = List(1)

差集diff

scala> list.diff(list1)
res22: List[Int] = List(2, 3, 4)

角標元素合併組合成元組zip

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

scala> list1
res24: List[Int] = List(1, 6)

scala> list.zip(list1)
res25: List[(Int, Int)] = List((1,1), (2,6))

 

8.5    偏函數

被包在花括號內沒有match的一組case語句

//演示偏函數  被包在花括號內沒有match的一組case語句
object PartialFunctionApp extends App {

  //正常的match..case匹配
  def baseMatch(name : String): Unit = name match{
    case "zhangsan" => "This is zhangsan"
    case "lisi" => "This is lisi"
    case _ => "I don't know who is it"
  }

  /*
   * 使用偏函數 def functionName:PartialFunction[入參類型,返回參數類型] = {
   *              case "case1" => "return1"
   *              case "case2" => "return2"
   *              case _       => "return3"
   *          }
   */
  def partialFunction:PartialFunction[String,String] = {
    case "zhangsan" => "This is zhangsan"
    case "lisi" => "This is lisi"
    case _ => "I don't know who is it"
  }

  //調用  傳遞入參即可
  println(partialFunction("zhangsan"))  //This is zhangsan
}

 

九    Scala中隱式(implicit)詳解

爲已存在的類添加一個新的方法,在Scala中通常使用隱式轉換解決

Scala中隱式類型轉換的初體驗。

scala> val a: Int = 3.14
<console>:11: error: type mismatch;
 found   : Double(3.14)
 required: Int
       val a: Int = 3.14
                    ^

scala> implicit def doubleToInt(d: Double) = d.toInt
warning: there was one feature warning; re-run with -feature for details
doubleToInt: (d: Double)Int

scala> val a: Int = 3.14
a: Int = 3

 在Scala中有一個很重要的類Predef,內部定義了大量的隱式轉換,如下爲自動的裝箱操作:

 implicit def int2Integer(x: Int) = java.lang.Integer.valueOf(x)

可以在scala控制檯中輸入:

scala> :implicit -v

 將返回Predef對象中69條的隱式轉換  /* 69 implicit members imported from scala.Predef */

implicit可以分爲:

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

 

9.1    隱式參數

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

//隱式參數
object ImplicitParam extends App {
  implicit val c: Int = 100
  //柯里化函數,scala中很多方法/函數都用到了implicit參數,不需要所有柯里化的參數都傳遞
  def sum(a: Int)(implicit b: Int) = a + b

  def implicitTest(implicit name: String): Unit = {
    println(s"Hello : $name")
  }

  /**implicitTest所定義的參數是隱式參數,在調用該方法時如果沒有傳遞參數,那麼編譯器在編譯時期會從上下文找一個隱式參數值
   *  需要注意的是 隱式參數的類型需要符合方法的入參類型
   */
  implicit val name: String = "zhangsan"
  //implicit val s: String = "s"    輸出結果 : Hello : s
  //如果同時書寫兩個implicit參數,在調用方法時候不傳遞,則會報錯,因爲不知道調用哪個參數

  implicitTest   //不傳遞參數,但結果爲   :   Hello : zhangsan

  sayHello   //Hello World
  println(sum(1)) //101
}

發現map方法的簽名爲一個柯里化函數,其中就包含了一個implicit參數,所以我們在使用map方法時一般只傳遞了一個參數。

final override def map[B, That](f: Int => B)(implicit bf: scala.collection.generic.CanBuildFrom[List[Int],B,That]): That
def map[B](f: Int => B): scala.collection.TraversableOnce[B] 

需要注意的是,implicit需要放在參數的末尾,如果方法的參數有多個隱式參數,只需要使用一個implicit關鍵字即可

def addPlus(a: Int)(implicit b: Int, c: Int) = a + b + c

 

9.2    隱式類型轉換

object ScalaImplicit {
  def sum(a: Int)(implicit b: Int) = a + b
  def addPlus(a: Int)(implicit b: Int, c: Int) = a + b + c


  //定義一個隱式的方法
  implicit def doubleToInt(double: Double) = {
    println("doubleToInt")
    double.toInt
  }

  //定義一個隱式的函數
  implicit val fdoubleToInt = (double: Double) => {
    println("fdoubleToInt")
    double.toInt
  }

  def main(args: Array[String]): Unit = {
    println("-------隱式類型轉換-------")
    val age: Int = 20.5   //fdoubleToInt  先找函數 後找方法
    println(age)          //20
  }
}

隱式轉換切面封裝
創建一個ImplicitAspect類,將隱式轉換語句拷貝到該類,最後再import ImplicitAspect._ 

//隱式轉換
object ImplicitApp extends App {
  //普通人只有喫的方法,那麼如何將超人飛的方法添加到普通人上呢? ————使用隱式轉換
  //套路 implicit def functionName(需要增強的類類型) : 返回含有增強方法的類類型 = new 含有增強方法的類()
  implicit def manTosuperMan(man:Man) : SuperMan = new SuperMan(man.name)

  //創建被增強對象
  var man = new Man("普通人");
  //發現man已包含了fly()方法
  println(  man.fly  )  //superman[普通人] can fly
}

//普通人只會喫
class Man(val name:String){
  def eat(): Unit ={
    println(s"Man[$name] only can eat")
  }
}

//超人會飛
class SuperMan(val name:String){
  def fly(): Unit ={
    println(s"superman[$name] can fly")
  }
}

File類中內置的方法不能返回文件信息的行數,通過定義MyFile類增強方法,並使用implicit關鍵字使File可以自動轉換爲RichFile類,那麼File類就"新增"了返回文件信息的行數的方法

import java.io.{BufferedReader, File, FileReader}

class RichFile(val file: File){
  def count(): Int = {
    val fileReader = new FileReader(file)
    val bufferReader = new BufferedReader(fileReader)
    var sum = 0
    try {
      var line = bufferReader.readLine()
      while (line != null) {
        sum += 1
        line = bufferReader.readLine()
      }
    } catch {
      case _: Exception => 0
    } finally {
      bufferReader.close()
      fileReader.close()
    }
    sum
  }
}

object FileToRichFile {
  //隱式類型轉換,達到增強File類中方法的效果
  //定義了一個隱式方法,將File類型變成RichFile類型
  implicit def fileToRichFile(file: File) = new RichFile(file)
  def main(args: Array[String]): Unit = {
    val file = new File("/Users/fatah/Desktop/20170815/part-m-00000")
    println(file.count)
  }
}

 

 

9.3    隱式類

隱式類只能在object靜態對象中使用,不能在class類中使用

使用隱式類增強File中的方法,實現使用的read()方法一次性讀取文件的所有內容

import java.io.File

import scala.io.Source
//隱式類 --只能在靜態對象中使用
object ScalaImplicitClass {
  //因爲包含在object內,所以不會報錯   隱式類
  implicit class FileRead(file: File){
    def read() = Source.fromFile(file).mkString
  }

  def main(args: Array[String]): Unit = {
    val file = new File("/Users/fatah/Desktop/20170815/part-m-00000")
    /**
      * 發現read()包含下劃線,所有隱式轉換的地方都會有下劃線
      * File類並沒有內置的read方法實際上調用的是FileRead類的read方法
      * 只不過隱式轉換對用戶是不可見的,內部發生的
      */
    println("內容爲:" + file.read())
  }
}

    

 

十    Scala操作外部數據

包括文件、網絡數據、MySQL、XML等外部數據

10.1   操作文件/網絡資源

//使用scala操作File文件
object FileTest {
  def main(args: Array[String]): Unit = {
    //讀取文件
    var file = scala.io.Source.fromFile("/Users/fatah/Desktop/1.txt")(scala.io.Codec.UTF8);
    //var file = scala.io.Source.fromFile("/Users/fatah/Desktop/1.txt");
    def readLine(): Unit = {
      for (line <- file.getLines()) {
        println(line)
      }
    }
    //調用讀取文件資源
    //readLine
    //調用讀取網絡資源
    readNet
    
    //讀取網絡數據
    def readNet(): Unit ={
      var file = scala.io.Source.fromURL("https://www.baidu.com");
      for(line <- file.getLines()){
        println(line)
      }
    }
  }
}

 

 

10.2   操作XML

//方式1
    var xml1 = XML.load(this.getClass.getClassLoader().getResource("test.xml"))
    //方式2
    val xml2 = XML.load(new FileInputStream("/Users/fatah/Desktop/Java開發工具/workspace/myscala/src/main/resources/test.xml"))
    println(xml1)

解析XML

Scala XML API提供了類似XPath的語法來解析XML。在NodeSeq這類父類裏,定義了兩個很重要的操作符("\"和"\\"),用來獲得解析XML:

 

在resources下創建test.xml 

  • \ :Projection function, which returns elements of this sequence based on the string that--簡單來說,\ 根據條件搜索下一子節點
  • \\:Projection function, which returns elements of this sequence and of all its subsequences, based on the string that--而 \\ 則是根據條件搜索所有的子節點
<fix major="4" minor="2">
  <header>
    <field name="BeginString" required="Y">FIX4.2</field>
    <field name="MsgType" required="Y">Test</field>
  </header>
  <trailer>
    <field name="Signature" required="N"/>
    <field name="CheckSum" required="Y"/>
  </trailer>
  <messages>
    <message name="Logon" msgtype="A" msgcat="admin">
      <field name="ResetSeqNumFlag" required="N"/>
      <field name="MaxMessageSize" required="N"/>
      <group name="NoMsgTypes" required="N">
        <field name="RefMsgType" required="N"/>
        <field name="MsgDirection" required="N"/>
      </group>
    </message>
    <message name="ResendRequest" msgtype="2" msgcat="admin">
      <field name="BeginSeqNo" required="Y"/>
      <field name="EndSeqNo" required="Y"/>
    </message>
  </messages>
  <fields>
    <field number="1" name="TradingEntityId" type="STRING"/>
    <field number="4" name="AdvSide" type="STRING">
      <value enum="X" description="CROSS"/>
      <value enum="T" description="TRADE"/>
    </field>
    <field number="5" name="AdvTransType" type="STRING">
      <value enum="N" description="NEW"/>
    </field>
  </fields>
</fix>

1. 首先來個簡單的,如果要找header下的field,那麼這樣寫即可:

val headerField = xml1 \"header"\"field"   //xml1對應的是你之前讀xml的變量名

2.找所有的field:

val field = xml1 \\"field"

3. 找特定的屬性(attribute),如找header下的所有field的name屬性的值:

val fieldAttributes = (xml1 \"header"\"field").map(_\"@name")

val fieldAttributes = xml1 \"header"\"field"\\"@name"

兩個都能找到header下面所有field的name屬性,但問題是輸出的格式不一樣。前者會返回一個List-List(BeginString, MsgType),而後者僅僅是BeginStringMsgType。中間連空格也沒有。所以建議用前一種方法獲得屬性。

之前以爲,下面的方法,和第二種方法一樣能夠獲得屬性的值:

val fieldAttributes = xml1 \"header"\"field"\"@name"

根據\操作符的定義,理論上應該可以輸出name屬性的。實際上輸出的結果是空,什麼也沒有。

\操作符的源碼裏有這麼一段:

that match {
      case ""                                         => fail
      case "_"                                        => makeSeq(!_.isAtom)
      case _ if (that(0) == '@' && this.length == 1)  => atResult
      case _                                          => makeSeq(_.label == that)
    }

第三個case表面,只有當this.length==1時,纔可以這樣做。原因其實很簡單,somXml\"header"\"field"返回的是一個Seq[Node]的集合,包含多個對象。而\"@"的操作無法確定操作哪一個對象的屬性:

val x = <b><h id="bla"/><h id="blub"/></b>
  val y = <b><h id="bla"/></b>
  println(x\\"h"\"@id") //Wrong
  println(y\\"h"\"@id") //Correct with output: bla

4. 查找並輸出屬性值和節點值的映射:

(xml1 \"header"\"field").map(n=>(n\"@name", n.text, n\"@required"))

這樣的輸出是List((BeginString,FIX4.2,Y), (MsgType,Test,Y))

5. 有條件地查找節點,例如查找name=Logon的message:

val resultXml1 = (xml1 \\"message").filter(_.attribute("name").exists(_.text.equals("Logon")))

val resultXml2 = (xml1 \\"message").filter(x=>((x\"@name").text)=="Logon")

6. 通過 \\"_" 獲得所有的子節點,例如:

println(resultXml1\\"_")
<message msgcat="admin" msgtype="A" name="Logon">
      <field required="N" name="ResetSeqNumFlag"/>
      <field required="N" name="MaxMessageSize"/>
      <group required="N" name="NoMsgTypes">
        <field required="N" name="RefMsgType"/>
        <field required="N" name="MsgDirection"/>
      </group>
</message>
<field required="N" name="ResetSeqNumFlag"/>
<field required="N" name="MaxMessageSize"/>
<group required="N" name="NoMsgTypes">
        <field required="N" name="RefMsgType"/>
        <field required="N" name="MsgDirection"/>
</group>
<field required="N" name="RefMsgType"/>
<field required="N" name="MsgDirection"/>

 

 

10.3    操作MySQL

//使用scala連接數據庫,操作MySQL
object MySQLTest {
  def main(args: Array[String]): Unit = {
    //書寫連接數據庫相關信息
    val url = "jdbc:mysql://localhost:3306/crash_course"
    val username = "root"
    val password = "root"

    //在scala中這一步與Java的class.forName寫法有所不同
    try {
      classOf[com.mysql.jdbc.Driver]
      val conn = DriverManager.getConnection(url,username,password)
      val sql = "select * from orders"
      val stmt = conn.prepareStatement(sql)
      val rs = stmt.executeQuery();
      while(rs.next()){
        val order_num = rs.getString("order_num")
        val order_date = rs.getString("order_date")
        val cust_id = rs.getString("cust_id")
        println(s"$order_num , $order_date , $cust_id")
      }
    }catch {
      case e : Exception => e.printStackTrace()
    }finally {
        //關閉資源,懶得關了,懂就行
    }
  }
}

 

十一    Scala中的泛型

11.1    泛型約束

import cn.itcats.GenericType.ShoesBrandEnum.ShoesBrandEnum
/**
  * 泛型: 就是對類型的約束
  */
abstract class Message[C](content: C)

class StrMessage[String](content: String) extends Message

class IntMessage[Int](value: Int) extends Message

//定義一個衣服的泛型類
class Shoes[A, B, C](val brand: A, val color: B, val size: C)

//創建一個枚舉類型
object ShoesBrandEnum extends Enumeration {
  type ShoesBrandEnum = Value //聲明枚舉對外暴露的類型
  val nike, adidas, skechers = Value
}

object ScalaGenericType {
  def main(args: Array[String]): Unit = {
    //創建幾款鞋子
    val type1 = new Shoes[ShoesBrandEnum, String, Int](ShoesBrandEnum.nike, "black", 42)
    println(type1.brand) //nike
    val type2 = new Shoes[ShoesBrandEnum, String, Double](ShoesBrandEnum.adidas, "white", 41.5)
    println(type2.size)  //41.5
  }
}

 

11.2    上界與下界約束

Upper Bounds

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

<T extends List>

//或者使用通配符的形式
<? extends List>

這種形式叫做upper bounds(上限或上界),同樣的意思在Scala中的寫法爲:

[T <: List]

//或者使用通配符
[_ <: List]

//例如: List中存儲的是Any類型的子類
def getEleFromList(list: List[T <: Any]){
    ele.foreach(print)
}

Lower Bounds

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

<T super Teacher>
//或者使用通配符
<? super Teacher>

這種形式也叫lower bounds(下限或下界),同樣的意思在Scala中的寫法爲:

[T >: Teacher]
[_ >: Teacher]

例子:

//只支持Int類型的比較
class CmpInt(a: Int, b: Int) {
  def bigger = if (a > b) a else b
}

//定義一個通用的比較器類
class CmpCommon[T <: Comparable[T]](t1: T, t2: T) {
  def bigger = if (t1.compareTo(t2) > 0) t1 else t2
}


object UpperLowerBounds {
  def main(args: Array[String]): Unit = {
    val cmpInt = new CmpInt(5, 10)
    println(cmpInt.bigger)

    val cmpCommon = new CmpCommon[Integer](1, 2) //發生隱式轉換
    println(cmpCommon.bigger)
  }
}

 

11.3    視圖界定

視圖界定會發生隱式轉換

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]
//定義一個通用的比較器類  <%  和  >% 都是視圖界定 
class CmpCommon[T <% Comparable[T]](t1: T, t2: T) {
  def bigger = if (t1.compareTo(t2) > 0) t1 else t2
}


object UpperLowerBounds {
  def main(args: Array[String]): Unit = {
    val cmpInt = new CmpInt(5, 10)
    println(cmpInt.bigger)
    val cmpCommon = new CmpCommon(1, 2) //視圖界定發生隱式轉換
    println(cmpCommon.bigger)
  }
}

 

視圖界定+隱式轉換的例子

//定義一個通用的比較器類 視圖界定,可以自動隱式轉換
class CmpCommon[T <% Comparable[T]](t1: T, t2: T) {
  def bigger = if (t1.compareTo(t2) > 0) t1 else t2
}

//注意Student並沒有顯式地實現Ordered特質
class Student(val name: String, val age: Int){
  override def toString = s"姓名: ${this.name} 年齡: ${this.age}"
}

object UpperLowerBounds {
  def main(args: Array[String]): Unit = {
    //發生隱式轉換,在視圖界定時候會自動掃描到該隱式轉換,將Student類型轉換爲Ordered[Student]類型,使Student可比較
    implicit def studentToOrderedStudent(stu: Student) = new Ordered[Student] {
      override def compare(that: Student): Int = stu.age - that.age
    }

    val zhangsan = new Student("zhangsan",18)
    val lisi = new Student("lisi",28)
    val cmpStu = new CmpCommon[Student](zhangsan,lisi)
    println(cmpStu.bigger)
  }
}

 

11.4    上下文界定

也會發生隱式轉換,實際上是視圖界定的語法糖

class Student(val name: String, val age: Int) {
  override def toString = s"姓名: ${this.name} 年齡: ${this.age}"
}

//上下文界定方式1
class CmpCommon[T: Ordering](t1: T, t2: T)(implicit cmptor: Ordering[T]) {
  def bigger = if (cmptor.compare(t1, t2) > 0) t1 else t2
}

//上下文界定方式2
class CmpCommon2[T: Ordering](t1: T, t2: T){
  def bigger = {
    def inner(implicit cmptor: Ordering[T]) = cmptor.compare(t1,t2)
    if(inner > 0) t1 else t2
  }
}

//上下文界定方式3
class CmpCommon3[T: Ordering](t1: T,t2: T){
  def bigger = {
    val cmptor = implicitly[Ordering[T]]
    if(cmptor.compare(t1,t2) > 0) t1 else t2
  }
}

object UpperLowerBounds {
  def main(args: Array[String]): Unit = {

    //一個隱式對象實例 -> 一個有具體實現的Comparator
    implicit val cmptor = new Ordering[Student] {
      override def compare(x: Student, y: Student) = x.age - y.age
    }

    val zhangsan = new Student("zhangsan", 18)
    val lisi = new Student("lisi", 28)
    val cmpStu = new CmpCommon(zhangsan, lisi)
    println(cmpStu.bigger)
  }
}

 

十二    排序

Java中的排序

public class OrderApp {
    public static void main(String[] args) {
        Phone phone1 = new Phone("iPhone XS", 5555.55);
        Phone phone2 = new Phone("HUAWEI P30", 5999.55);
        Phone phone3 = new Phone("HUAWEI MATE30", 6299.55);
        List<Phone> phones = new ArrayList<>();
        phones.add(phone1);  phones.add(phone2);  phones.add(phone3);

        //比較三款手機的價格 sort無返回值
        Collections.sort(phones, new Comparator<Phone>() {
            @Override
            //按照降序排列  價格高的在前 價格低的在後
            public int compare(Phone o1, Phone o2) {
                if(o1.getPrice() > o2.getPrice())
                    return -1;
                else if(o1.getPrice() < o2.getPrice())
                    return 1;
                else return 0;
            }
        });
        //使用foreach查看結果
        phones.stream().forEach(x -> {
            System.out.println(x.getBrand());
        });
    }
}

Scala中的排序[與Java中的對應關係]

Java            --->    Scala
Comparable      --->    Ordered
Comparator      --->    Ordering
trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializable {}
trait Ordered[A] extends Any with java.lang.Comparable[A] {}
object OrderedPhone extends App{
  private val orderImpl = new OrderImpl[Ticket](new Ticket("歡樂谷",220),new Ticket("長隆",280))
  println(orderImpl.smaller().name) //歡樂谷
  println(orderImpl.bigger().name) //長隆
}

//泛型T 爲 Ordered[T] 的子類
class OrderImpl[T <: Ordered[T]](val first: T, val second: T){
  //比較的時候直接使用 < 符號 進行對象間的比較
  //它最終調用的是 Ticket 中 compare 方法,來比較出兩個值的大小
  def smaller() = {
    if(first < second) first else second  //內置的 < 方法
  }
  def bigger() = {
    if(first.>(second)) first else second //內置的 > 方法
  }

}

class Ticket(val name: String, val price: Int) extends Ordered[Ticket]{
  override def compare(that: Ticket) = {
    this.price - that.price
  }

}

 

 

十三    使用Scala編寫WordCount

scala> var arr = Array("hello good nice bye","hello hi good bye")
arr: Array[String] = Array(hello good nice bye, hello hi good bye)

scala> arr.flatMap(x => x.split(" "))
res3: Array[String] = Array(hello, good, nice, bye, hello, hi, good, bye)

scala> arr.flatMap(x => x.split(" ")).groupBy(x => x)
res4: scala.collection.immutable.Map[String,Array[String]] = Map(bye -> Array(bye, bye), good -> Array(good, good), nice -> Array(nice), hi -> Array(hi), hello -> Array(hello, hello))

scala> arr.flatMap(x => x.split(" ")).groupBy(x => x).map(x => x._2)
res5: scala.collection.immutable.Iterable[Array[String]] = List(Array(bye, bye), Array(good, good), Array(nice), Array(hi), Array(hello, hello))

scala> arr.map(_.split(" ")).flatten.groupBy(x => x).map(yz => (yz._1, yz._2.length))
res6: scala.collection.immutable.Map[String,Int] = Map(bye -> 2, good -> 2, nice -> 1, hi -> 1, hello -> 2)

scala> arr.flatMap(x => x.split(" ")).groupBy(x => x).mapValues(x => x.length)
res7: scala.collection.immutable.Map[String,Int] = Map(bye -> 2, good -> 2, nice -> 1, hi -> 1, hello -> 2)

 

scala> var arr = Array("hello","itcats","hello","itcats_cn","hello","cn")
arr: Array[String] = Array(hello, itcats, hello, itcats_cn, hello, cn)

scala> arr.map((_,1))
res0: Array[(String, Int)] = Array((hello,1), (itcats,1), (hello,1), (itcats_cn,1), (hello,1), (cn,1))

scala> arr.map((_,1)).groupBy(_._1)
res1: scala.collection.immutable.Map[String,Array[(String, Int)]] = Map(itcats -> Array((itcats,1)), cn -> Array((cn,1)), itcats_cn -> Array((itcats_cn,1)), hello -> Array((hello,1), (hello,1), (hello,1)))

scala> arr.map((_,1)).groupBy(_._1).mapValues(_.length)
res2: scala.collection.immutable.Map[String,Int] = Map(itcats -> 1, cn -> 1, itcats_cn -> 1, hello -> 3)

scala> arr.map((_,1)).groupBy(_._1).mapValues(_.length).toList
res3: List[(String, Int)] = List((itcats,1), (cn,1), (itcats_cn,1), (hello,3))

scala> arr.map((_,1)).groupBy(_._1).mapValues(_.length).toList.sortBy(- _._2)
res4: List[(String, Int)] = List((hello,3), (itcats,1), (cn,1), (itcats_cn,1))

 

scala> arr
res10: Array[String] = Array(hello word hello java hello scala java, hello hi)

scala> arr.flatMap(_.split(" "))
res11: Array[String] = Array(hello, word, hello, java, hello, scala, java, hello, hi)

scala> arr.flatMap(_.split(" ")).map(x => (x,1))
res13: Array[(String, Int)] = Array((hello,1), (word,1), (hello,1), (java,1), (hello,1), (scala,1), (java,1), (hello,1), (hi,1))

scala> arr.flatMap(_.split(" ")).map(x => (x,1)).groupBy(_._1)
res14: scala.collection.immutable.Map[String,Array[(String, Int)]] = Map(java -> Array((java,1), (java,1)), scala -> Array((scala,1)), hi -> Array((hi,1)), hello -> Array((hello,1), (hello,1), (hello,1), (hello,1)), word -> Array((word,1)))

scala> arr.flatMap(_.split(" ")).map(x => (x,1)).groupBy(_._1).mapValues(t =>t.foldLeft(0)(_ + _._2))
res17: scala.collection.immutable.Map[String,Int] = Map(java -> 2, scala -> 1, hi -> 1, hello -> 4, word -> 1)

scala> arr.flatMap(_.split(" ")).map(x => (x,1)).groupBy(_._1).mapValues(t =>t.foldRight(0)(_._2 + _))
res18: scala.collection.immutable.Map[String,Int] = Map(java -> 2, scala -> 1, hi -> 1, hello -> 4, word -> 1)

 

Scala代碼:

object WordCount extends App {

  def wordCountApp(): Unit = {
    var arr: Array[String] = Array.apply("hello good nice bye", "hello hi good bye")
    var arr_arr: Array[Array[String]] = arr.map((str: String) => str.split(" "))
    var flatArr: Array[String] = arr_arr.flatten
    //Map[String,Array[String]]   hello -> Array(hello,hello)
    var groupMap: Map[String,Array[String]] = flatArr.groupBy((wd: String) => wd)
    val yzRes: Map[String,Int] = groupMap.map(yz => (yz._1,yz._2.length))

    //map結構中不能sort,需要轉換爲List
    //toList後的結果 List[(String, Int)] = List((bye,2), (good,2), (nice,1), (hi,1), (hello,2))
    val sortRes: List[(String,Int)] = yzRes.toList.sortBy(t => t._2)
    sortRes.foreach(println)
  }
  wordCountApp()
}

 

 

可以藉助spark-shell直接讀取文件

scala> sc.textFile("/Users/fatah/Desktop/wc.txt").flatMap(_.split(" "))

 

scala> sc.textFile("/Users/fatah/Desktop/wc.txt").flatMap(_.split(" "))
res24: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[75] at flatMap at <console>:25

scala> sc.textFile("/Users/fatah/Desktop/wc.txt").flatMap(_.split(" ")).collect
res25: Array[String] = Array(hello, itcats, hello, itcats_cn, hello, cn)

最終基於Spark的WordCount代碼如下: 

scala> sc.textFile("/root/w.txt").flatMap(_.split(" ")).map((_, 1)).reduceByKey(_+_).collect

 

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