Scala編程快速入門系列(二)

来源:https://www.cnblogs.com/yangp/archive/2018/03/17/8584066.html
-Advertisement-
Play Games

本文較為詳細地介紹了Scala的理論,並結合相應的代碼進行解讀與實踐。文章內容主要包含Scala的簡介,數據類型,函數(函數定義、匿名函數、嵌套函數、迴圈語句等),集合(List、Tuple、Map),伴生對象,trait,Actor,隱式轉換,JDBC等。 ...


目    錄

一、Scala概述

二、Scala數據類型

三、Scala函數

四、Scala集合

五、Scala伴生對象

六、Scala trait

七、Actor

八、隱式轉換與隱式參數

九、Scala JDBC

由於整理的篇幅較長,所以文章計劃分三次發佈。第一部分的內容請轉至系列(一)

四、Scala集合

1. Scala集合概述

  Scala對集合的操作就是Spark程式的實現方式。Spark中有一個RDD(Resilience彈性的、Distributed分散式、DataSet數據集),spark的程式都是將源數據載入過來變成一個RDD,然後每一步操作都是集合的元素進行操作。對於Spark來說是分散式的操作,但是對於寫程式來說無需考慮分散式,只需考慮對集合元素的操作。Scala的集合操作是針對單機的,Spark是針對分散式的,但是代碼都類似。

2. List集合

  這裡舉例創建集合和獲取集合元素的同時也有一些常用的集合操作函數。

  • 創建List集合

   在Scala中創建一個集合不需要new關鍵字。

object TestCollection {
  val list =List(1,4,6,4,1)
}
  • 獲取集合的元素
object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    println("獲取集合的第2個元素:"+list(1))//集合的下標從0開始
  }
}

  使用list(),括弧中傳入集合元素的位置來獲取集合元素。 

  • map函數

  map函數的本質就是使用匿名函數對集合中每一個元素做同樣的操作。

object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    val b= list.map(a=>{println(a+"-----");a+1})
    val c= list.map(_+1)
    println(b)
    println(c)
  }
}

  list.map是集合list調用map方法,map方法對集合的每個元素進行操作,具體的操作由匿名函數定義。第一個map函數中的a代表集合List的每一個元素,作為匿名函數的參數,執行方法體列印,然後返回最後一行a+2賦給新的集合相應位置的元素。

  list.map(_+1))//這樣寫是上式的簡寫形式,下劃線代表集合的每一個元素。

  • "+:"和":+"函數向集合添加元素
object TestCollection {
  val list =List(1,4,6,4,1)
  val list2=list.+:("楊輝三角")
  val list3=list.:+("楊輝三角")
  def main(args: Array[String]): Unit = {
    println(list2)
    println(list3)
  }
}

  +: 在集合第一個位置添加元素;:+ 在集合最後一個位置添加元素。運行結果如下:

  List(楊輝三角, 1, 4, 6, 4, 1)
  List(1, 4, 6, 4, 1, 楊輝三角) 

  • foreach函數遍歷輸出

  foreach和map很相似,都是對集合的每一個元素做相應的操作,只是map會返回值給集合。如果要列印結果一般用foreach。

object TestCollection {
  val list =List(1,4,6,4,1)
  val list2=list.+:("楊輝三角")
  def main(args: Array[String]): Unit = {
    list2.foreach(i => print("---"+i))//是對集合list2中的每一個元素遍歷,i表示集合中的每一個元素。
    list2.foreach(i => {val j = i + "s"; print("---"+ j)})//可以對集合中的元素先進行有關操作
    list2.foreach(print _)//對集合遍歷輸出可以簡化為此式
  }
}
  • distinct函數去重
object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    list.distinct.foreach(print _)
  }
}

  輸出結果為:146

  • slice函數截取集合

   slice函數需要兩個參數,第一個參數表示從該下標開始截取,第二個參數表示截取到該下標(不包含)。

object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    print(list.slice(0,3))
  }
}

  結果為:List(1, 4, 6)

  • for迴圈遍歷集合
object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    for(i <- list){
      print(i)
    }
  }
}
  • length函數獲取集合長度
object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    for(i <- 0.to(list.length-1)){
      print(list(i))
    }
  }
}
  • "/:"函數
object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]) {
    //list./:是調用./方法,它是一個柯里化函數,其中(100)是第一個參數,({(sum,num)=>print(sum+"--"+num+" ");sum-num})是第二個參數。
    println(list./:(100)({
      (sum,num)=>print(sum+"--"+num+" ");//函數/:的第二個參數——匿名函數需要兩個參數,匿名函數第一個參數為/:函數的第一個參數,匿名函數的返回值類型和/:函數的第一個參數類型一致
        sum+num//這裡的匿名函數實際上是(sum,num)=>sum-num,就是傳入兩個參數sum和num,返回sum+num,返回值的類型顯然和sum的類型一樣。/:方法詳見源碼解讀。
    }));//返回結果為100--1 101--4 105--6 111--4 115--1 116
  }
}
//源碼片段:
def /:[B](z: B)(op: (B, A) => B): B = foldLeft(z)(op)

def foldLeft[B](z: B)(op: (B, A) => B): B = {
  var result = z
  this foreach (x => result = op(result, x))
  result
}

  源碼解讀:如下代碼是/:函數的源碼,可見/:是個柯里化函數。其中,[B]是函數/:的泛型;(z: B)是第一個參數,其類型為泛型[B];(op: (B, A) => B)是第二個參數,它是一個匿名函數op,它需要兩個參數(B, A),能返回B類型的值。最後的:B是函數/:的返回值類型。/:(z)(op)=foldLeft(z)(op)。

  再看foldLeft函數,也是一個柯里化函數,需要兩個參數,參數類型和/:的參數類型一致。其方法體可見,首先將傳入的第一個B類型的參數z賦值給變數result,然後調用該方法的當前對象(如集合List對象)使用foreach(這裡的this foreach和this.foreach是一樣的道理)方法遍歷當前對象中的所有元素,其元素x的類型就是匿名函數的第二個參數的類型A,這裡調用匿名函數op,以result和x為參數,其返回結果賦值給result,通過多次調用匿名函數迴圈集合的所有元素,最後返回result,作為函數foldLeft的返回值,也就是函數/:的返回值。

  • reduce函數

   reduce函數和/:函數很類似,使用的頻率很高。

object TestCollection {
  val list =List(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    //reduce函數,需要一個匿名函數做參數,此匿名函數的類型是(A1,A1)=>A1,匿名函數第一次的參數是前兩個元素;之後,第一個參數上一次的匿名函數返回值,第二個參數是依次位置的集合元素值。
    println(list.reduce((a: Int, b: Int) =>{println(a + "---" + b) ; a+b}))//最終結果是所有元素的和16
    //上式求所有元素的和和以簡化為下麵的形式
    println(list.reduce(_+_))
  }
}
//源碼片段:
def reduce[A1 >: A](op: (A1, A1) => A1): A1 = reduceLeft(op)
def reduceLeft[B >: A](op: (B, A) => B): B = {
  if (isEmpty)
    throw new UnsupportedOperationException("empty.reduceLeft")

  var first = true
  var acc: B = 0.asInstanceOf[B]

  for (x <- self) {
    if (first) {
      acc = x
      first = false
    }
    else acc = op(acc, x)
  }
  acc
}

  源碼解讀:reduce函數最終是要調用reduceLeft函數,顧名思義是需要從左側開始。reduceLeft函數需要一個匿名函數(op: (B, A) => B),返回類型是B,和傳入的第一個參數一樣。if (isEmpty)如果集合為空,拋出異常。0.asInstanceOf[B],B是一個泛型,0是int類型,0.asInstanceOf[B]意為將0轉為B這個泛型類型。在else acc = op(acc, x)中才開始調用傳入的匿名函數op。 

3. Tuple元組

  • 創建元組

  在Scala中創建元組不需要關鍵字,只需要括弧就行。它的特點是,定義了元組之後,元組的值不可以修改(和Python一致)。

object TestCollection {
  val tuple =(1,4,6,4,1)
}
  • 獲取元組元素
object TestCollection {
  val tuple =(1,4,6,4,1)
  def main(args: Array[String]): Unit = {
    println(tuple._1)//元組取值是用"._",不能像list集合一樣用括弧
    println(tuple._5)//Tuple元組下標從1開始。
  }
} 

4. Map集合

  Scala中的Map有兩種類型,一個是Mutable可以更改的,另一個是Immutable不可更改的。如果沒有導包直接寫Map的話是預設為Immutable的,如果要創建可以更改key的value值的Map集合需要導包,指定是Mutable的Map。

  • 創建Map集合
object TestCollection {
  //定義一個map集合,[String,Int]分別是鍵和值的泛型。
  var map = Map[String, Int]("a" -> 1, "b" -> 2);//使用“->”來定義一對key value,每對key/value使用逗號隔開。
  var map2 = Map[String, Int](("a", 3), ("b", 4));//也可以使用(key,value)的形式定義一對key/value,因為Map中的每一個元素都是一個元組。
}
  • 獲取集合元素
object TestCollection {
  var map = Map[String, Int]("a" -> 1, "b" -> 2);
  def main(args: Array[String]) {
    println(map("a"));//使用(鍵)來獲取對應的值
  }
}
  • "+="函數添加集合元素
object TestCollection {
  var map = Map[String, Int]("a" -> 1, "b" -> 2);
  map += ("c" -> 3)
  map += Tuple2.apply("d",4)
  def main(args: Array[String]) {
    println(map);//輸出結果為Map(a -> 1, b -> 2, c -> 3, d -> 4)
  }
}
  • foreach函數遍歷集合
object TestCollection {
  var map = Map[String, Int]("a" -> 1, "b" -> 2);
  def main(args: Array[String]) {
    map.foreach(kv=>{
      println(kv+" "+kv._1+" "+kv._2)//這裡的kv是集合m1的每一個元素,它是一組鍵值對,在Scala中是一個元組,所以要取得每一個元素的鍵和值可以使用元組的取值方法,kv._1獲得kv的鍵,kv._2獲得kv的值。
    })//其結果為(a,1) a 1\n(b,2) b 2
  }
}
  • keys迭代器
object TestCollection {
  var map = Map[String, Int]("a" -> 1, "b" -> 2);
  def main(args: Array[String]): Unit = {
    map.keys.foreach(k => println(map(k)))//map.keys獲得map的所有keys,返回一個迭代器;然後可以使用foreach遍歷,也可以在通過鍵獲取值。
  }
}

五、Scala伴生對象

1. 伴生對象的概念

  所謂伴生,就是在語言層面上,把static成員和非static成員用不同的表達方式,class(非靜態成員)和object(靜態成員),但雙方具有相同的包名和命名(class_name和object_name可以完全一樣),編譯器會把他們編譯到一起。編譯會生成.class文件,編譯時會把名稱相同的class非靜態的和object靜態的編譯到一起。

2. Object&Class

  • 案例一
class Test{
  var field = "field" //類的屬性
  def doSomeThing = println("do something")//類的方法,調用需要new 對象後才可以調用
}

object Test{
  val a = "a string" //伴生對象的屬性
  def printAString = println(a)//這個方法是靜態的,可以使用Test.printString來調用。
}

  編譯這個文件,同樣生成兩個class,一個TEST.class和一個Test$.class,這個Test$.class叫做虛構類。

  • 案例二(靜態方法和屬性)
class TestObject {
  val str = "Good!"
  def func() = {
    println("Hello World!");
  }
}

object TestObject {
  val str= 100;
  val single = new TestObject();
  def func() = {//定義在object里是靜態方法
    println("Hello Scala!");
  }
  /**
    * main函數是static的,main函數如果定義在class中會當做普通函數,函數名為main而已。
    */
  def main(args: Array[String]) {
    //創建class的實例需要用new關鍵字
    val t1 = new TestObject();
    println(t1.str);//調用實例的str屬性
    t1.func();//調用實例的func01函數

    TestObject.func();//Object名.靜態方法名。
    println(TestObject.str);//Object名.靜態屬性名。
  }
}

  執行結果如下:

Good!
Hello World!
Hello Scala!
100
  • 案例三(構造器)
class TestConstructor(val a: Int, val b: Int) {//class類後面的小括弧,是預設的構造器
  var x = a;//把參數a和b賦給變數
  var y = b;

  def this(xArg: Int) { //this也是構造器,在方法體裡面要調用預設的構造器。
    this(xArg, 123);
    println("I'm this constructor");
  }
}

object TestConstructor {//這個對象是伴生著這個類出來的,所以叫伴生對象
  def main(args: Array[String]) {
    val p1 = new TestConstructor(321);//使用 this(xArg: Int)構造器
    println(p1.x)//321
    println(p1.y)//123
    val p2 = new TestConstructor(222, 333);//使用Point(val x: Int, val y: Int)構造器
    println(p2.x)//222
    println(p2.y)//333
  }
}

六、Scala trait

  trait可以認為是一種特性,但是不等同於Java中的介面,因為Java介面中沒有實現的方法,train可以有實現了的方法(方法體),trait的作用在於提取封裝共性,供各種類型的類共同使用。用法詳見下麵例子:

trait Listen {//和java的介面很像,但是不能new trait名。
  val name: String //定義變數name
  def listen() = {
    println( name + " is listening")
  }
}
trait Read {
  val name: String
  def read() = {
    println(name + " is reading")
  }
}
trait Speak {
  val name: String
  def speak() = {
    println(name + " is speaking.")
  }
}
class Human(val name: String) {//預設構造器需要一個屬性:名字
  def speak() = {//方法
    println("Look, "+name + " is speaking.")
  }
}
class Animal(val name: String) {}//創建一個動物類,構造器也是一個參數名字。
//extends繼承Animal類。Dog(override val name: String),重寫name;with關鍵字可以加上trait特性。和Java中的繼承類,實現介面類似。
class Dog(override val name: String) extends Animal(name: String) with Speak with Listen with Read {
  //重寫一個方法只需在方法def前面加關鍵字override。
  override def toString(): String = "Hello, My name is " + name + "! "
}
object TestTrait {
  def main(args: Array[String]) {
    //創建Human類的對象,命名為張三。hi調用Human類的speak方法。
    val h1 = new Human("張三")
    h1.speak()
    //創建Dog對象,調用了trait中listen方法和speak方法。
    val dog = new Dog("八公")
    dog.listen()
    dog.speak()
    //調用Dog類重寫了的toString方法。
    println(dog)
  }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 整理自官網教程 -- https://cn.vuejs.org/ 利用v-model可以實現表單元素的value與後臺數據的雙向綁定,具體用法如下: 可為v-model添加修飾符: v-model.lazy -- 將輸入事件轉變為使用 change 事件進行同步。 .number -- 自動將值轉換 ...
  • <a id="s" href="##" onclick='doSomething("${dsMbTable.getTableId}","${dsMbTable.dsManageTable.tableId}","${dsMbTable.id}","${dsStrategyId}")'>策略關聯表欄位選 ...
  • 1.模型管理 :web線上流程設計器、預覽流程xml、導出xml、部署流程 2.流程管理 :導入導出流程資源文件、查看流程圖、根據流程實例反射出流程模型、激活掛起 3.運行中流程:查看流程信息、當前任務節點、當前流程圖、作廢暫停流程、指派待辦人 4.歷史的流程:查看流程信息、流程用時、流程狀態、查看 ...
  • 2. 服務的路由和負載均衡 1.2.1 服務化的演變 SOA設計思想:分散式應用架構體系對於業務邏輯復用的需求十分強烈,上層業務都想借用已有的底層服務來快速搭建更多、更豐富的應用,降低新業務開展的人力和時間成本,快速滿足瞬息萬變的市場需求。公共的業務被拆分出來行程可共用的服務,最大程度的保障了代碼和 ...
  • 1.1 基於TCP協議的RPC 1.1.1 RPC名詞解釋 RPC的全稱是Remote Process Call,即遠程過程調用,RPC的實現包括客戶端和服務端,即服務調用方和服務提供方。服務調用方發送RPC請求到服務提供方,服務提供方根據請求的參數執行請求方法,並將結果返回給服務調用方,一次RPC ...
  • 我們公司有個很牛逼的女生,是我們項目組的核心人物之一,一般我有啥不懂的就問她,就在昨天我們討論一件關於緩存的事情,我覺得在類中,用static定義的引用變數就是緩存(因為在以前的公司就這麼叫),而她認為這並不是,這個只是存在記憶體里的,這隻是一個成員變數,所以他覺得只能叫變數,牛逼的大佬們你們覺得叫什 ...
  • 1.下載Java 地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 安裝到電腦 2.配置環境 在 控制面板\所有控制面板項\系統 高級系統設置->環境變數 JAVA_HOME ...
  • 換了一家公司,感覺好對不起以前那些領導的栽培和培養啊,但是沒辦法,我真的受不了那些煙味和每天加班到好晚好晚的工作環境,也許這就是所謂的不合吧。。 到了新的一家公司,明顯比剛出來的時候要好太多,別人一說什麼東西我都會有反應,而且不會一臉懵b的問別人還不知道別人說了些啥。。感覺👌。 閑話不多說,先看在 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...