大數據框架(處理海量數據/處理實時流式數據) 一:以hadoop2.X爲體系的海量數據處理框架 離線數據分析,往往分析的是N+1的數據 - Mapreduce 並行計算,分而治之 - HDFS(分佈式存儲數據) - Yarn(分佈式資源管理和任務調度) 缺點: 磁盤,依賴性太高(io) shuffle過程,map將數據寫入到本次磁盤,reduce通過網絡的方式將map task任務產生到HDFS - Hive 數據倉庫的工具 底層調用Mapreduce impala - Sqoop 橋樑:RDBMS(關係型數據庫)- > HDFS/Hive HDFS/Hive -> RDBMS(關係型數據庫) - HBASE 列式Nosql數據庫,大數據的分佈式數據庫 二:以Storm爲體系的實時流式處理框架 Jstorm(Java編寫) 實時數據分析 -》進行實時分析 應用場景: 電商平臺: 雙11大屏 實時交通監控 導航系統 三:以Spark爲體系的數據處理框架 基於內存 將數據的中間結果放入到內存中(2014年遞交給Apache,國內四年時間發展的非常好) 核心編程: Spark Core:RDD(彈性分佈式數據集),類似於Mapreduce Spark SQL:Hive Spark Streaming:Storm 高級編程: 機器學習、深度學習、人工智能 SparkGraphx SparkMLlib Spark on R Flink
Spark 學習計劃 第一部分:scala編程語言 第二部分:Spark Core(最重要的內容)-》 概念RDD:相當於Mapreduce 第三部分:Spark Sql:相當於Hive,支持sql語句 - 》 底層依賴的Spark Core -》依賴RDD 第四部分:Spark Streaming:相當於Storm - 》底層依賴Spark Core -》依賴RDD 注意:但是Spark Streaming不能做到實時性非常高 學習建議: - 類比法 Scala:Java SPark Core:mapreduce Spark Sql:Hive streaming: Core, 基於Core Api的封裝 - 聽、練、記、理解 - 聽:認真聽 - 練:自己敲代碼、不要拷貝 - 記:筆記 - 思:思考、理解 ===============第一部分:Scala編程語言======================================== 一:scala語言基礎 *)scala簡介 1)多範式編程語言 面向對象編程+面向函數式編程 2)編程簡單 Java vs Scala編程語言 代碼量:10:1 3)scala語言誕生了兩個大數據框架,而且非常重要的大數據框架 - spark 分佈式海量數據處理框架 - kafka 分佈式流平臺,基於消息的發佈訂閱隊列框架,存儲數據 3)學習網站 https://www.scala-lang.org - 官網 http://twitter.github.io/scala_school/zh_cn/ - 推特 4)版本的選擇 2.11.x版本, 此處爲2.11.12 備註說明: - Spark 1.6.x版本 推薦的scala 2.10.x版本 - Spark 2.x版本 推薦的Scala 2.11.x版本 *)scala的安裝 1)windows平臺 *)JDK 8 *)以scala-2.11.12.zip爲例 1:解壓縮:D:\developer\scala-2.11.12 2:設置SCALA_HOME:D:\developer\scala-2.11.12 3:將%SCALA_HOME%\bin加入到PATH路徑 4:執行:scala -version 2)linux平臺 *)JDK 8 *)跟安裝JDK步驟相同 *)寫一個scala程序 1)scala語言是基於JVM之上的語言 *.java 編譯 *.class -> JVM *.scala 編譯 *.class -> JVM #創建scala文件 vi HelloScala.scala #編寫scala程序 object HelloScala { def main(args: Array[String]): Unit = { println("Hello World !!!") } } #編譯scala代碼 scalac HelloScala.scala #執行scala程序 scala HelloScala *)常用開發工具 1)REPL(Read Evaluate Print Loop):命令行 2)IDE:圖形開發工具 The Scala IDE (Based on Eclipse):http://scala-ide.org/ IntelliJ IDEA with Scala plugin:http://www.jetbrains.com/idea/download/ Netbeans IDE with the Scala plugin *)scala數據類型 1)在scala中,任何數據都是對象 舉例:數字1 -》是一個對象,就有方法 scala> 1.toString res0: String = 1 2)數據類型:複習 Byte: 8位的有符號 -128~127 Short: 16位的有符號的 -32768~32767 Int: 32位的有符號的 Long: 64位的有符號的 Float: 浮點型 Double: 雙精度
3)var聲明變量,variable 簡寫,表示的變量,可以改變值 4)val聲明變量, value 簡寫,表示的意思爲值,不可變.常量 5)對於字符串來說,在scala中可以進行插值操作
scala> var str = s"Hello ${name}" str: String = Hello Tom
6)lazy 使用 scala> lazy val i = 10 i: Int = <lazy> scala> i res3: Int = 10 舉例:讀取文件(文件不存在的話)
scala> lazy val words = scala.io.Source.fromFile("d:\\aa.txt").mkString words: String = <lazy> 7)在scala中操作符就是方法 scala> 1.+(2) res3: Int = 3 在scala中不支持三種操作符 ++ / --/ ?: 自增 自減 三目 scala中的操作符實際上就是scala中方法的調用,只不過爲了簡潔期間,將方法的調用轉換爲中綴表達式 Scala中操作符實際上是方法。例如: a + b 是如下方法調用的簡寫: a+(b) a 方法 b可以寫成 a.方法(b) 8)給變量賦
scala> var a:String = _ a: String = null scala> var b:Int = _ b: Int = 0 scala> var b:Double = _ b: Double = 0.0 #如果使用默認值的話,一定指明變量的類型,否則報錯 String類型的默認值是null Int類型的默認值是0 Double類型的默認值是0.0 9)Nothing表示在程序運行中產生了Exception scala> def f = throw new Exception("Someing") f: Nothing 如果返回值是Nothing,表示返回了異常 注意:在Scala中,定義變量可以不指定類型,因爲Scala會進行類型的自動推導 *)scala的條件表達式 IF 判斷來說有三種結構: -1, IF IF(boolean) { } -2, IF...ELSE... IF(boolean) { //TODO true do something } ELSE { //TODO false do something } -3,IF...ELSE IF...ELSE IF(i==1) { //TODO true 1 something } ELSE IF(i==2){ //TODO false 2 something } ELSE { //TODO false 0 something } def main(args: Array[String]): Unit = { } #Unit表示無返回值,相當於Java中的void 塊表達式 在scala中{}中課包含一系列表達式,塊中最後一個表達式的值就是塊的值 *)scala的循環 For 循環 循環表達式 在JAVA中進行循環的時候 for(int i = 0; i< 10; i++) While 循環表達式 while loop 先判斷再執行 Do While loop 先執行後判斷,至少執行一次 For循環: scala> for(s <- str) println(s) a b c d
scala> val list = Array("Hadoop", "Spark", "Hive") list: Array[String] = Array(Hadoop, Spark, Hive)
scala> for(a <- list) println(a) #to:左閉右閉區間 scala> 1 to 10 res4: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
#Range:左閉右開區間 scala> Range(1, 10) res5: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5, 6, 7, 8, 9)
#Range:左閉右開區間,生成1-9之間數字,其中參數2是步長 scala> Range(1, 10, 2) res7: scala.collection.immutable.Range = Range(1, 3, 5, 7, 9)
#until:左閉右開區間 scala> 1 until 10 res6: scala.collection.immutable.Range = Range(1, 2, 3, 4, 5, 6, 7, 8, 9) 二:scala面向函數式編程(最有特色的一部分)-》將函數作爲函數的參數傳遞過去 (*)方法和函數的區別 1.方法: 相對於OOP來說 類class 屬性:名詞 方法:動詞,如一個函數在類中,則該函數爲方法 2.函數: 不在類中的方法,稱此方法爲函數 將函數作爲函數的參數傳遞過去 3.OOP編程中 比如JAVA語言來說,方法必須在類中,不能脫離class獨立存在 但是FP(函數式編程)中, 函數可以獨立存在,不需要依賴類class 4.SCALA語言,既可以面向對象編程,又可以面向函數式編程,所以類和函數,對象都是“一等公民”,地位相同,都可以獨立存在,不需要依賴任何類和對象 (*)如何表示一個方法: def m(x:Int, y:Int): Int - def:聲明一個方法 - m:方法名(如果沒有方法名,那麼這個方法是匿名方法) - (x:Int, y:Int):左邊的參數(指明瞭參數的個數和類型) - : Int:函數的返回值(返回值類型) (*)定義一個方法 scala> def m(x:Int, y:Int):Unit = x+y m: (x: Int, y: Int)Unit
//如果不指名返回值類型,則根據方法體進行自動推導 scala> def m2(x:Int, y:Int) = { x + y } m2: (x: Int, y: Int)Int //無返回值 scala> def m3(x:Int, y:Int) = { println("") } m3: (x: Int, y: Int)Unit
//不傳遞參數 scala> def m5() = println(100) m5: ()Unit
//scala中,若無參數傳遞,則可以不加() scala> m5 100 //定義的時候,若不需要傳遞參數,則不需要加() scala> def m6 = print(100) m6: Unit
//若定義的時候不加(), 調用時也不能加() 注意:方法的返回值類型可以不寫,編譯器可以自動推斷出來,但是對於遞歸方法,必須指定返回類型 (*)如何定義一個函數 #val: 定義一個函數 #=>: 函數裏面使用=> #省略返回值 #func: 函數名稱 #(Int, Int) :函數參數列表 #=> Int:函數返回值 #<function2>:函數參數個數, 最多只能有22個,如果想使用更多的參數,使用變長參數 scala> val func = (x: Int, y:Int) => x*y func: (Int, Int) => Int = <function2> #不指名函數名稱,則稱爲匿名函數 scala> (x:Int) => x*2 res7: Int => Int = <function1>
#無參函數 scala> () => 2 res8: () => Int = <function0>
#輸出函數簽名 scala> res8 res9: () => Int = <function0>
#調用函數 scala> res8() res10: Int = 2
#簡單寫法 scala> val f2 = (x:Int, y:Int) => x * y f2: (Int, Int) => Int = <function2>
#完整寫法, #f3:函數名稱 #(Int, Int):函數參數類型 #=> Int:函數返回值類型 #{(x, y) => x * y}:函數體,(參數名+函數體)組成 scala> val f3:(Int, Int) => Int = {(x, y) => x * y} f3: (Int, Int) => Int = <function2> (*)scala的數組、集合、元組 1)數組 scala> val v1 = Array(1,2,3,4,5,8) v1: Array[Int] = Array(1, 2, 3, 4, 5, 8) #數組裏面既可以放Int, 也可以放String,都繼承自Any,在scala中所有類型都繼承自Any
scala> val v3 = Array(1,2,3,"Tom") v3: Array[Any] = Array(1, 2, 3, Tom) #顯式指定數據類型,不可以放其他類型數據
#動態初始化一個數組,這裏的5是數組的長度, #對於Int來說,初始化的默認值是0 #對於String來說,初始化的默認值是null scala> val v4 = new Array(5) v4: Array[Nothing] = Array(null, null, null, null, null)
scala> val v4 = new Array[String](5) v4: Array[String] = Array(null, null, null, null, null)
scala> val v5 = new Array[Int](5) v5: Array[Int] = Array(0, 0, 0, 0, 0)
#取得集合總值,在scala中是在java基礎上又一次進行高度的封裝,方便用戶使用 scala> v5.sum res5: Int = 23 #升序排序 scala> v5.sorted res8: Array[Int] = Array(0, 0, 10, 12, 13) #降序排序 scala> v5.sorted.reverse res9: Array[Int] = Array(13, 12, 10, 0, 0)
#通過匿名函數進行排序 scala> v5.sortBy(x => x) res10: Array[Int] = Array(0, 0, 10, 12, 13)
scala> v5.sortBy(x => -x) res11: Array[Int] = Array(13, 12, 10, 0, 0) 2)集合 #定義一個集合 scala> val ls = List(1,2,3,4,5) ls: List[Int] = List(1, 2, 3, 4, 5) scala> ls.length scala> ls.sum scala> ls(1) 3)Map scala> val map = Map("a"-> 1, "b"->2, "c"->3) #指定類型 scala> val map2 = Map[String, Int]("a"-> 1, "b"->2, "c"->3)
#取得不存在的key,如果沒有返回一個默認值0, 避免程序報錯 scala> map.getOrElse("d", 0) res17: Int = 0
#遍歷映射中所有的鍵/值對偶 scala> for((k, v) <- map) { | println(k + " " + v) | } a 1 b 2 c 3
4)元組 1)定義 使用一堆圓括號包含起來 2)下標 訪問元組中元素下標從1開始 3)元素個數 最多22個元素 4)數據類型 任意的scala語言支持的數據類型 #一個簡單的元組 scala> val t = (1, "a", 2.0, 5L) t: (Int, String, Double, Long) = (1,a,2.0,5)
下標從1開始 元組的好處: 1:可以放多種類型數據,在java中返回多個參數,需要將參數放到一個集合或者寫個model實體類,返回該實體對象,但是在scala中可以放到元組中非常方便
#map中存放很多的對偶元組 scala> val m1 = Map(("a", 100), ("b", 200)) m1: scala.collection.immutable.Map[String,Int] = Map(a -> 100, b -> 200) (*)scala函數的參數:求值策略 1、call by value:對函數的實參求值,並只求值一次 舉例:def test1(x:Int, y:Int):Int = x+x 沒有用到y 2、call by name:函數的實參每次在函數體內部被調用的時候,都會進行求值 舉例:def test2(x: => Int, y: => Int):Int = x + x 沒有用到y 3:、一個複雜的例子 x是call by value y是call by name def test3(x:Int, y: => Int):Int = 1 在定義一個函數 def loop():Int = loop 分別進行調用 scala> test3(1, loop) -->正常 res31: Int = 1 scala> test3(loop, 1) -> 死循環 (*)使用Scala實現WordCount詞頻統計程序(單機) #map函數使用說明 scala> val arr = Array(1,2,3,4,5,6,7,8,9) arr: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> var arr2 = arr.map((x:Int)=> x*10) arr2: Array[Int] = Array(10, 20, 30, 40, 50, 60, 70, 80, 90)
scala> var arr2 = arr.map(_*10) arr2: Array[Int] = Array(10, 20, 30, 40, 50, 60, 70, 80, 90) scala> var arr = Array("Spark Hadopp Hive", "Hive Hbase", "Sqoop Redis Hadoop") arr: Array[String] = Array(Spark Hadopp Hive, Hive Hbase, Sqoop Redis Hadoop)
#將元素進行拆分, 拆分後每個元素("Spark Hadopp Hive")形成獨立的小數組 scala> var arr2 = arr.map(x => x.split(" ")) arr2: Array[Array[String]] = Array(Array(Spark, Hadopp, Hive), Array(Hive, Hbase), Array(Sqoop, Redis, Hadoop))
#壓平操作,將子數組的元素壓破,flatMap=flatten+map scala> var arr4 = arr.flatMap(x => x.split(" ")) arr4: Array[String] = Array(Spark, Hadopp, Hive, Hive, Hbase, Sqoop, Redis, Hadoop)
#將每個元素做一次計數 scala> var arr3 = arr2.map(x => (x, 1)) arr3: Array[(String, Int)] = Array((Spark,1), (Hadopp,1), (Hive,1), (Hive,1), (Hbase,1), (Sqoop,1), (Redis,1), (Hadoop,1)) scala> var arr4 = arr3.groupBy(x => (x._1)) arr4: scala.collection.immutable.Map[String,Array[(String, Int)]] = Map( Sqoop -> Array((Sqoop,1)), Hbase -> Array((Hbase,1)), Hive -> Array((Hive,1), (Hive,1)), Hadopp -> Array((Hadopp,1)), Spark -> Array((Spark,1)), Redis -> Array((Redis,1)), Hadoop -> Array((Hadoop,1)) ) #對數據進行統計 scala> val arr5 = arr4.map(x => (x._1, x._2.length)) arr5: scala.collection.immutable.Map[String,Int] = Map(Sqoop -> 1, Hbase -> 1, Hive -> 2, Hadopp -> 1, Spark -> 1, Redis -> 1, Hadoop -> 1) (*)函數的進一步說明 #可以將函數作爲方法的參數進行傳遞,也可以將方法作爲方法的參數傳遞,但是程序本身會做隱形轉換 scala> val arr = Array(1,2,3,5,8) scala> val f = (x: Int) => x*x f: Int => Int = <function1>
scala> def m(x:Int) = x*x m: (x: Int)Int
scala> arr.map(f) res15: Array[Int] = Array(1, 4, 9, 25, 64)
scala> arr.map(m) res16: Array[Int] = Array(1, 4, 9, 25, 64)
#顯式將方法轉換爲函數 scala> m _ res19: Int => Int = <function1>
#將數組的元素小寫轉大寫 scala> var arr = Array("tom", "mary", "mike") scala> def toUpper(str:String):String = str.toUpperCase toUpper: (str: String)String
scala> arr.map(toUpper) res20: Array[String] = Array(TOM, MARY, MIKE)
#簡化的寫法,這時候傳遞的是簡化版的函數,而非方法 scala> def m(x:Int) = x*x m: (x: Int)Int
scala> arr.map(x => m(x)) res24: Array[Int] = Array(1, 9, 16, 36, 49)
scala> arr.map(m(_)) res25: Array[Int] = Array(1, 9, 16, 36, 49)
#根據自己的業務實現一個過濾器 scala> def m1(x:Int,y:Int) = x*y m1: (x: Int, y: Int)Int
scala> m1 _ res26: (Int, Int) => Int = <function2>
scala> def ft(x:Int):Boolean = x%2 ==0 ft: (x: Int)Boolean
scala> arr.filter(ft(_)) res27: Array[Int] = Array(4, 6) (*)函數的默認值參數 函數的參數默認值,建議有默認值的參數放到參數列表的最後 def sayHello(name:String, msg:String="Hi !"): Unit = { println(msg + " " + name) } sayHello("Tom", "Hello") ->正常 sayHello("Tom") ->正常,如果沒有傳遞msg參數則使用默認參數 調用函數式爲可以指定函數的參數進行賦值,指定的名稱要與函數聲明時參數名稱相同 def sayHello2(name:String = "Tom"): Unit = { println("Hello , " + name) } sayHello2() -> 正常 sayHello2 -> Error sayHello2(name = "Mike") ->正常 (*)函數的可變參數 # 定義變長參數的函數 # String* 表示接受一系列的String類型的值,類似於java語言的可變參數 # 內部來說:變長函數的類型實際上是一個數組,比如String*,Array[String] def printCourses(courses:String*): Unit = { //courses.foreach(x => println(x)) courses.foreach(println) } (*)一個高階函數的案例 idea寫個程序進行演示 (*)閉包 1)簡單的例子 閉包是一個函數,它返回值取決於在此函數之外聲明的一個或多個變量的值。
#函數賦值給addMore,該函數就是一個閉包 scala> val addMore = (x:Int) => x + more <console>:11: error: not found: value more val addMore = (x:Int) => x + more ^ scala> val addMore = (x:Int) => x + more addMore: Int => Int = <function1>
scala> addMore(10) res3: Int = 11
#在閉包創建以後,閉包之外的變量more修改以後,閉包中的引用也會隨之變化,因此Scala的閉包捕獲的是變量本身而不知當時變量的值 scala> more = 10 more: Int = 10
scala> addMore(10) res5: Int = 20 #閉包之外的變量修改會影響閉包中相應的變量,同樣,在閉包中修改閉包外的變量,則閉包外的變量也會跟着變化 scala> val some = List(1,3,10,-10) some: List[Int] = List(1, 3, 10, -10)
scala> var sum = 0 sum: Int = 0
scala> some.foreach( sum += _ )
scala> sum res7: Int = 4 2)複雜一點的例子 #這裏的more相當於閉包參數,要在調用的時候才能確定 scala> def make(more:Int) = (x:Int) => x+more make: (more: Int)Int => Int //方法由三部分組成,make(方法名)、(more: Int)Int(列表參數+返回值)+返回類型 #調用時確定閉包參數more爲1,且返回函數值,並賦值給inc1 scala> val inc1 = make(1) inc1: Int => Int = <function1> scala> val inc99 = make(99) inc99: Int => Int = <function1> 上面每次make函數調用時都會產生一個閉包,且每個閉包都會有自己的more變量值 //下面纔是真正的調用函數,且各自都有自己的閉包參數more scala> inc1(10) //閉包參數more值爲1 res8: Int = 11
scala> inc99(10) //閉包參數more值爲99 res9: Int = 109 (*)柯里化 1)概念:柯里化是將方法或者函數中一個帶有多個參數的列表拆分成多個小的參數列表(一個或者多個參數)的過程,並且將參數應用前面參數列表時返回新的函數 scala> def sum(x:Int, y:Int) = x+ y sum: (x: Int, y: Int)Int
scala> sum(2,4) res17: Int = 6
#將sum寫成柯里化的sum,前面方法使用一個參數列表,“柯里化”把方法或者函數定義成多個參數列表(且第一個參數只有一個參數,剩餘的參數可以放在一個參數列表中) scala> def sum(x:Int)(y:Int) = x+y sum: (x: Int)(y: Int)Int
scala> sum(2)(4) res18: Int = 6 #當你調用sum(2)(4)時, 實際上是依次調用了兩個普通函數(非柯里化函數) //第一次調用使用一個參數,x 返回一個函數值 //第二次使用參數y調用這個函數值,下面來用兩個分開的定義來模擬sum柯里化函數的調用過程 scala> def first(x:Int) = (y:Int) => x+y first: (x: Int)Int => Int //frist返回的是函數值,x既是方法參數,又是函數的閉包參數
#調用first方法會返回函數值,既產生第二個函數 scala> val second = first(1) //產生了第二個函數 second: Int => Int = <function1> //second是函數變量名,引用某個函數值 scala> second(2) res21: Int = 3
#上面first,second的定義演示了柯里化函數的調用過程,他們本身和sum並沒有任何關係,但是
scala> val second = sum(1) _ //sum(1)相當於第二方法的方法名 second: Int => Int = <function1> scala> second(2) res22: Int = 3
注意: scala> val second = sum(1) _ second: Int => Int = <function1> scala> val func = sum _ //這裏是將整個sum方法轉換爲函數,該函數帶有兩個參數,而前面知識將方法sum的一部分轉換爲函數(既第二個列表參數),所以上面只帶有一個參數 func: Int => (Int => Int) = <function1> 三:scala面向對象編程 (*)scala的類的定義 複習:面向對象的基本概念 1)定義:把數據和操作數據的方法放到一起,作爲一個整體(類class) 2)面向對象的特質 (1)封裝 (2)繼承 (3)多態 3)scala既是面向函數式編程也是面向對象編程->多範式 (*)作用域 1)默認就是public 2)跟java語言類似 3)一般情況下使用 public 公共的,任意都可以訪問 private 私有,表示當前類的對象(所有)都可以訪問 private[this] 當前對象(實例)都可以可以訪問,其他對象不能訪問 private[pageage]: 表示某個包下面都可以訪問,比如private[Spark],表示在spark包下所有的類都可以訪問 (*)類的解析 對於Java和scala來說,運行程序必須main方法中 - 對JAVA語言來說,main method在class類中 public static void main(String[] args) *.java -> compile -> *.class -> JVM - 對於scala語言來說,main method在object中 def main(args: Array[String]) {} *.scala -> compile -> *.class -> JVM val 只會生成get方法 var 生成set和get方法 (*)伴生對象 1)如果有一個class,還有一個與class同名的object,那麼就稱這個object是class的伴生對象,class是object的伴生類 2)伴生類和伴生對象必須存放在一個.scala文件之中 3)伴生類和伴生對象,最大的特點就在於,互相可以訪問private field (*)構造器 類似於java中的構造方法 1)主構造器( class Boy(val name:String) ) * 在scala中,主構造器是與類名放在一起的,有且只有一個,java可以寫多個構造方法,多個構造方法間實現重載 * 在類中,沒有定義在任何方法中的代碼(包括成員字段),都屬於主構造器的代碼,且執行順序於代碼書寫的順序是一致的,其實與java一樣 * 在java中方法之外的代碼(成員及代碼塊),在構造器調用之前最先執行,姑且將這些代碼看做也是一個主構造器中進行執行的 * 主構造器還可以通過使用默認參數,來給參數默認的值 2)輔助構造器( def this(name:String, age:Int) ) * 輔助構造可以有多個 * 多個輔助構造器之間可以調用 * 輔助構造中的參數不可以加val或者var 3)私有構造器 * 私有構造器是針對於主構造器的 * 如果聲明瞭私有構造器,那麼只能被他的伴生對象訪問 * 如果主構造器設置爲私有構造器,那麼輔助構造器依然可以訪問(訪問權限) (*)scala中的object 1)object:相當於Java中的靜態類,類似於java單例模式,通常在裏面放一些class層面共享的內容 2)可以將object看做是一個類class,只是這個類在內存中只有一個單例,且定義的object就是實例名,不需要我們自己實例化,運行於JVM,在jvm中幫我們new出來了 3)第一次調用object方法時,會執行object構造器,也就是說object內部不在method中的代碼(並且只執行一次),但是object不能定義接受參數的構造器 4)注意object的構造器只會在第一次調用時執行,以後再次調用不會再執行構造器了 在scala中可以用object實現: 作爲存放工具函數或者常量的地方 高效的共享單個不可變實例 單例模式 【一個簡單的工具類】 object DateUtils { def getCurrentDate: String = getCurrDateTime("EEEE, MMMM d") def getCurrentTime: String = getCurrDateTime("K:m aa") private def getCurrDateTime(dateTimeFormat:String) :String = { val dateFormat = new SimpleDateFormat(dateTimeFormat) val cal = Calendar.getInstance() dateFormat.format(cal.getTime) } } 【實現一個單例模式】 /** * 實現一個單例模式 * 構造函數被我們定義爲private的目的:防止直接調用該類來創建對象 * */ class StaticTest private { private def add_(x:Int, y:Int): Int = { return x + y } } /** * 這個就是單例模式的定義,和類同名,且不帶參數 * */ object StaticTest { //內部聲明一個StaticTest類實例對象 val staticTest = new StaticTest //調用StaticTest類的方法 def add(x:Int, y:Int):Int = { return staticTest.add_(x, y) } }
object StaticTest2{ def main(args: Array[String]): Unit = { //調用,定義一個單例對象 val static = StaticTest //調用add方法 println(static.add(2,3)) } } (*)scala的apply方法 1)當不是new關鍵字來創建對象的時候,使用apply可以使我們的代碼更簡潔 class Person { var name: String = _ var age:Int = 0 } object Person{ def apply(name:String): Person = { val person = new Person person.name = name person }
def apply(name:String, age:Int): Person = { val person = new Person person.name = name person.age = age person } } 現在可以不使用new關鍵字來創建Person對象了 val person2 = Person("Mike") val person3 = Person("Mary", 23) scala編譯器會對伴生對象中apply進行特殊化處理,讓你不使用new關鍵字即可創建對象 (*)繼承 1)scala中,讓子類繼承父類,與java一樣,使用extends關鍵字 2)繼承就代表,子類可以從父類繼承父類的field和method,然後子類可以在自己內部放入父類所沒有,子類特有的filed和method,使用繼承可以複用代碼 3)子類可以覆蓋父類的filed和method,但是要注意的是final關鍵字,代表field和method無法覆蓋 4)子類中的方法要覆蓋父類中的方法,必須寫override(參見foo) 5)子類中的屬性val要覆蓋父類中的屬性,必須寫override(參見nameVal) 6)父類中的變量不可以覆蓋(參見nameVar) 4)定義抽象類 abstract class Animal { //定義一個抽象類,用於繼承 var age :Int = 2 val weight:Double = 35
//抽象方法,沒有具體的實現 def color()
//非抽象方法,有具體的實現 def eat()={ println("吃食物") } //使用了final關鍵字,表示不能重寫, override final def action():Unit = { println ("奔跑") } } /** * 如果想實現父類的方法:CTRL+I * 如果想重寫父類的方法:CTRL+O */ class Monkey extends Animal{ //重寫父類字段 override var age:Int = 15 override val weight: Double = 15
//抽象方法,沒有實現(重寫父類抽象的方法,可以選擇性的使用override) override def color(): Unit = { println("棕色") } //非抽象方法有實現了,(重寫父類非抽象方法,必須使用override) override def eat(): Unit = { println("吃桃子") } } object Monkey{ def main(args: Array[String]): Unit = { val monkey = new Monkey println(monkey.weight) println(monkey.color()) println(monkey.eat()) println(monkey.action()) } } (*)trait:特質 1)scala 特徵:相當於Java中的接口,實際上他比接口功能強大. 2)與接口不同的是:是可以定義屬性和方法的實現 3)一般情況下scala的類只能被繼承單一父類,但是如果是trait的話可以實現多個繼承,從結果上來看就是實現了多繼承 4)trait定義的方式與類類似,但是它使用的關鍵字是trait,通過extends來繼承的,用with實現多繼承 (*)多態 1)什麼是多態:目的是爲了讓代碼更加,降低耦合 有繼承或實現特質(接口) 父類引用指向子類對象或接口指向實現類 方法需要重寫 abstract class Element { def demo(): Unit ={ println("Element's invoked") } } class ArrayElement extends Element{ override def demo(): Unit = { println("ArrayElement's invoked") } } class LineElement extends ArrayElement{ override def demo(): Unit = { println("LineElement's invoked") } } class UniforElement extends Element //沒有重寫父類方法
object ElementTest{ //參數類型爲祖宗類,任何子類實例都可以傳遞(基類) def invokedDemo(e:Element): Unit ={ e.demo() //多態,在運行時調用相應子類方法 } def main(args: Array[String]): Unit = { invokedDemo(new ArrayElement) //父類引用指向子類對象 invokedDemo(new LineElement) //祖父類引用指向孫類對象 invokedDemo(new UniforElement) //沒有重寫父類方法,所以調用的時候輸出祖宗類demo } } 四:scala的集合 (*)數組 java中的集合都是可變集合 在scala中集合分爲可變集合和不可變集合 可變數組默認可以使用 不可變數組必須引用:import scala.collection.mutable.ArrayBuffer 不可變數組:長度不可改變, 但是內容可以改變 #添加一個元素 scala> arrBuffer += 10 res5: arrBuffer.type = ArrayBuffer(10)
#根據值刪除,如果元素存在則直接remove,否則忽略(不報錯) scala> arrBuffer -= 10 res10: arrBuffer.type = ArrayBuffer(20, 30, 40) #追加一組元素,使用List或者數組或者元組(需要用++=) scala> arrBuffer ++= List(20,30,40,50) res12: arrBuffer.type = ArrayBuffer(20, 30, 40, 20, 30, 40, 50)
#調用mkString方法,將數組中的元素組合成一個字符串,並且將各個元素之間使用指定的分隔符進行分割 scala> array.mkString(",") res13: String = 20,30,40,20,30,40,50
#在字符串前後追加一個符號<> scala> array.mkString("<", ",", ">") res14: String = <20,30,40,20,30,40,50> (*)集合 scala中的集合 Seq[T](List[T]、Set[T]、Map[T]) -1:都是泛型 類型而言,具體業務具體對待(看存儲的數據) -2:分爲可變和不可變 在Java中所有的集合(List、Map、Set)都是可變的 不可變:集合中元素的個數不可改變,元素值不能改變 常見的三種集合: 可變的: List Map Set 不可變的: ListArray Map Set 包不同 可變的集合: scala.collection.mutable._ 不可變的集合: scala.collection.immutable._ 默認使用的集合類型,是不可變得,但是可以在程序中導入包即可使用
1)List scala> val lst = List(1,2,3,5) lst: List[Int] = List(1, 2, 3, 5)
scala> lst(1) res2: Int = 2
scala> lst(3) res3: Int = 5
#修改元素內容拋出異常 scala> lst(3) = 10 <console>:13: error: value update is not a member of List[Int] lst(3) = 10
#修改集合長度拋出異常 scala> lst += 16 <console>:13: error: value += is not a member of List[Int] Expression does not convert to assignment because receiver is not assignable. lst += 16 ^ #引入可變集合包 scala> import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
#可以看到這是可變的集合,但是一定要先引入 import scala.collection.mutable.ListBuffer包後才能使用 scala> val lb = new ListBuffer[Int]() lb: scala.collection.mutable.ListBuffer[Int] = ListBuffer()
#追加一條數據 scala> lb.append(1)
scala> lb res10: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1)
#可以修改裏面的值 scala> lb(0) = 10
scala> lb res3: scala.collection.mutable.ListBuffer[Int] = ListBuffer(10) #也是可以這樣追加 scala> lb += 20 res4: lb.type = ListBuffer(10, 20)
#也可以追加一個元組 scala> lb += (30,40,50) res5: lb.type = ListBuffer(10, 20, 30, 40, 50)
#也可以追加一個List, 但是需要++= scala> lb ++= List(60,70,80) res7: lb.type = ListBuffer(10, 20, 30, 40, 50, 60, 70, 80)
#也可以追加一個Array scala> lb ++= Array(90, 100) res8: lb.type = ListBuffer(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
#刪除一個元素 scala> lb -= 90 res9: lb.type = ListBuffer(10, 20, 30, 40, 50, 60, 70, 80, 100)
#根據下標刪除 scala> lb.remove(0) res10: Int = 10
#刪除一個元組 scala> lb -= (20,30) res12: lb.type = ListBuffer(40, 50, 60, 70, 80, 100)
#匹配上進行刪除,無匹配的則忽略 scala> lb -= (20, 40) res13: lb.type = ListBuffer(50, 60, 70, 80, 100)
2)Map #不可變集合Map scala> val mp = Map("a"->1, "b"->2) mp: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2) #導入所有的可變集合包,這裏使用的下滑線, #下劃線可以代表通配符 scala> import scala.collection.mutable._ import scala.collection.mutable._ #可變集合Map scala> val mp = Map("a"->1, "b"->2) mp: scala.collection.mutable.Map[String,Int] = Map(b -> 2, a -> 1)
#增加keyvalue,長度是可變的 scala> mp.put("c", 3) res14: Option[Int] = None
scala> mp res15: scala.collection.mutable.Map[String,Int] = Map(b -> 2, a -> 1, c -> 3)
#修改mp的內容,內容也是可變的 scala> mp("c") = 4 #加一個對偶元組,可以("d"->5)這樣寫 scala> mp += ("d"->5) res19: mp.type = Map(b -> 2, d -> 5, a -> 1, c -> 4)
#刪除一個key,或者寫成mp.remove("d") scala> mp -= "d" res21: mp.type = Map(b -> 2, a -> 1, c -> 4)
3)Set #定義一個不可變的set scala> val lmst = Set(1,2,3,4,5) lmst: scala.collection.immutable.Set[Int] = Set(5, 1, 2, 3, 4)
scala> lmst += 12 <console>:13: error: value += is not a member of scala.collection.immutable.Set[Int] Expression does not convert to assignment because receiver is not assignable. lmst += 12 ^ #定義一個可變的Set,不能重複,會把重複的給過濾掉,保留一份 #Set添加的數據是無序的 scala> val imst = Set(1,2,3,4,2) imst: scala.collection.mutable.Set[Int] = Set(1, 2, 3, 4)
#添加一個數據 scala> imst +=7 res2: imst.type = Set(1, 2, 3, 7, 4) #添加一個元組 scala> imst += (10,14,54) res3: imst.type = Set(1, 2, 3, 54, 10, 7, 4, 14)
scala> imst ++= st res5: imst.type = Set(12, 1, 100, 2, 3, 54, 10, 7, 4, 14)
#刪除一個數據 scala> imst -= 12 res6: imst.type = Set(1, 100, 2, 3, 54, 10, 7, 4, 14) 4)scala的集合總結 數組、集合 數組的特點: 可變數組->長度和內容都可以改變 不可變數組->長度不能變,內容可以變 集合的特點: 可變集合:長度和內容都可以改變 不可變集合:長度內容都不能改變,適用場景:多線程 追加一個元素: += 移除一個元素:-= 追加一個類型一樣集合:++= (*)集合的常用方法: 1)reduce介紹 scala> val arr = Array(1,2,5,7,10) arr: Array[Int] = Array(1, 2, 5, 7, 10)
scala> arr.reduce((x, y) => x+y) res7: Int = 25 #簡單寫法 scala> arr.reduce(_+_) res8: Int = 25
scala> arr.reduce(_*_) res9: Int = 700
scala> arr.reduce(_-_) res10: Int = -23
#注意:從左到右的順序依次計算 scala> arr.reduce(_-_) //默認使用reduceLeft,從左到右計算 res10: Int = -23
scala> arr.reduceLeft(_+_) res11: Int = 25 scala> arr.reduceRight(_+_) //表示從列表尾部開始,對兩兩元素進行求和操作 res12: Int = 25 scala> arr.reduceLeft(_-_) res13: Int = -23
scala> arr.reduceRight(_-_) res14: Int = 7 舉例: scala> val list = List(1,2,3,4,5) list: List[Int] = List(1, 2, 3, 4, 5) scala> list.reduce(_ - _) res29: Int = -13 //可以看出,得到的結果和reduceLeft的結果是一樣的
#list.reduceLeft(_ - _), reduceLeft步驟解析 ((((1-2)-3)-4)-5) ((((-1)-3)-4)-5) (((-4)-4)-5) ((-8)-5) (-13) #list.reduceRight(_ - _), reduceRight步驟解析 val list = List(1,2,3,4,5) (1-(2-(3-(4-5))) (1-(2-(3-(-1))) (1-(2-(4))))) (1-(-2)) (3) (*)fold方法 意思是疊加(中文摺疊),reduce是聚合的意思,從本質上說,fold函數是將一種格式的輸入數據轉換成另外一個格式數據返回 scala> val list = List(1,2,3,4,5) list: List[Int] = List(1, 2, 3, 4, 5) scala> list.reduce(_+_) res0: Int = 15 #這裏0初始值,也是後面高階函數的柯里化 scala> list.fold(0)((x, y) => x+y) res1: Int = 15 #默認值是100 scala> list.fold(100)((x, y) => x+y) res2: Int = 115 #步驟解析 (((((100+1)+2)+3)+4)+5) ((((101+2)+3)+4)+5) (((103)+3)+4)+5) ((106+4)+5) (110+5) 115 foldLeft(從左到右計算) foldRight(從右到左計算)
#默認值是字符串類型,List是數字集合的時候,不能使用fold,使用foldLeft或者foldRight scala> list.fold("Hello")(_+_) <console>:13: error: type mismatch; found : Any required: String list.fold("Hello")(_+_) ^
scala> list.foldLeft("Hello")(_+_) res5: String = Hello12345 #從右到左進行計算 scala> list.foldRight("Hello")(_+_) res6: String = 12345Hello
#字符串與數字進行數學運算則報錯,而+除外,+可以表示字符串拼接轉換 ^ (*)sortBy(排序僅僅是改變了數據的順序,而無法改變數據的類型) scala> val list = List(1,2,4,-7,12,90) list: List[Int] = List(1, 2, 4, -7, 12, 90)
scala> list.sortBy(x=>x) res8: List[Int] = List(-7, 1, 2, 4, 12, 90)
scala> list.sortBy(x=> -x) res9: List[Int] = List(90, 12, 4, 2, 1, -7) (*)sorted scala> list.sorted res10: List[Int] = List(-7, 1, 2, 4, 12, 90) scala> list.sorted.reverse res11: List[Int] = List(90, 12, 4, 2, 1, -7) (*)sortWith(兩兩比較,類似於冒泡排序) scala> list.sortWith(_>_) res12: List[Int] = List(90, 12, 4, 2, 1, -7) scala> list.sortWith(_<_) res13: List[Int] = List(-7, 1, 2, 4, 12, 90) (*)flatten(壓平,將大的list中的小list集合進行壓平) #list中沒有小的list,報錯 #每四個元素進行一次分組 scala> var it = list.grouped(4) it: Iterator[List[Int]] = non-empty iterator
scala> var lst = it.toList lst: List[List[Int]] = List(List(1, 2, 4, -7), List(12, 90))
#壓平 scala> lst.flatten res16: List[Int] = List(1, 2, 4, -7, 12, 90) (*)flatMap = flatten+map scala> var lines = Array("spark hadoop hive", "hive hbase redis hive spark", "scala java java") lines: Array[String] = Array(spark hadoop hive, hive hbase redis hive spark, scala java java)
[第一種寫法] scala> lines.flatMap(line => line.split(" ")).map(x=>(x, 1)).groupBy(x => x._1).map(x => (x._1, x._2.map(_._2).sum)) res21: scala.collection.immutable.Map[String,Int] = Map(java -> 2, hadoop -> 1, spark -> 2, hive -> 3, scala -> 1, redis -> 1, hbase -> 1)
[第二種寫法] map返回的是key+value scala> lines.flatMap(line => line.split(" ")).map(x=>(x, 1)).groupBy(x => x._1).map(x => (x._1, x._2.foldLeft(0)(_+_._2))) scala> lines.flatMap(line => line.split(" ")).map(x=>(x, 1)).groupBy(x => x._1).map(x => (x._1, x._2.foldLeft(0)((x, y) => x + y._2))) res22: scala.collection.immutable.Map[String,Int] = Map(java -> 2, hadoop -> 1, spark -> 2, hive -> 3, scala -> 1, redis -> 1, hbase -> 1) [第三種寫法] mapvalues返回的是value _.foldLeft(0)(_+_._2) scala> lines.flatMap(line => line.split(" ")).map(x=>(x, 1)).groupBy(x => x._1).mapValues(_.foldLeft(0)(_+_._2)) res28: scala.collection.immutable.Map[String,Int] = Map(java -> 2, hadoop -> 1, spark -> 2, hive -> 3, scala -> 1, redis -> 1, hbase -> 1)
[第四種寫法]
val arr5 = arr4.map(x => (x._1, x._2.length))
步驟解析 scala> lines.flatMap(line => line.split(" ")).map(x=>(x, 1)).groupBy(x => x._1) res23: scala.collection.immutable.Map[String,Array[(String, Int)]] = Map( java -> Array((java,1), (java,1)), hadoop -> Array((hadoop,1)), spark -> Array((spark,1), (spark,1)), hive -> Array((hive,1), (hive,1), (hive,1)), scala -> Array((scala,1)), redis -> Array((redis,1)), hbase -> Array((hbase,1)) )
1:foldLeft解析 scala> val arr = Array(("java",1), ("java",1), ("java", 2)) arr: Array[(String, Int)] = Array((java,1), (java,1), (java,2))
scala> arr.foldLeft(0)(_+_) <console>:13: error: overloaded method value + with alternatives: (x: Int)Int <and> (x: Char)Int <and> (x: Short)Int <and> (x: Byte)Int cannot be applied to ((String, Int)) arr.foldLeft(0)(_+_) ^
scala> arr.foldLeft(0)((x, y) => x + y._2) res26: Int = 4
(*)模式匹配 可以匹配的類型: 1)匹配內容 //這裏的=>不是函數,在這裏表示模式匹配,如果匹配上則執行這裏的業務邏輯 //類比法 JAVA : switch case object CaseDemo01 extends App { val arr = Array("tom", "mike", "hello") val i = Random.nextInt(arr.length) println(i) val name = arr(i) println(name)
//這裏的=>不是函數,在這裏表示模式匹配,如果匹配上則執行這裏的業務邏輯 //類比法 JAVA : switch case name match { case "tom" => println("我是tom...") case "mike" => { println("我是mike'...") } case _ => println("真不知道你們在說什麼...") } } 2)匹配類型 object CaseDemo02 extends App { //定義一個數組 val arr: Array[Any] = Array("hello123", 1, -2.0, 2.0, CaseDemo02, 2L)
//取出一個元素 val elem = arr(5) println(elem)
//這裏的=>不是函數,在這裏表示模式匹配,如果匹配上則執行這裏的業務邏輯
elem match { case x: Int => println("Int " + x) case y: Double if (y <= 0) => println("Double " + y) case z: String => println("String " + z) case w: Long => println("long " + w) case CaseDemo02 => { val c = CaseDemo02 println(c) println("case demo 2") //throw new Exception("not match exception") } case _ => { //表示什麼都沒匹配上,相當於switch中的default println("no") println("default") } } } 3)匹配Array println("******************匹配數組****************************") val arr = Array(1, 1, 7, 0) arr match { case Array(0, 2, x, y) => println(x + " " + y) case Array(1, 1, 7, 0) => println("0 ...") case Array(1, 1, 7, y) => println("only 0 " + y) case _ => println("something else") } 4)匹配元組 println("******************匹配元組****************************") val tup = (1, 3, 8) tup match { case (1, x, y) => println(s"hello 123 $x , $y") case (_, w, 5) => println(s"w:${w}") case _ => println("else") } (*)樣例類 1)簡單來說,scala的class,就是在普通類定義前加個case關鍵字,然後你就可以對這些類進行模式匹配 case class帶來的最大的好處就是支持模式匹配 //樣例類,模式匹配,封裝數據(多例),不用new即可創建實例 case class SubmitTask(id: String, name: String)
case class HeartBeat(time: Long)
//樣例對象,模式匹配(單例) case object CheckTimeOutTask
/** * 樣例類demo * 簡單來說,scala的class,就是在普通類定義前加個case關鍵字,然後你就可以對這些類進行模式匹配 * case class帶來的最大的好處就是支持模式匹配 */ object CaseDemo04 extends App {
val arr = Array(CheckTimeOutTask, new HeartBeat(123), HeartBeat(88888), new HeartBeat(666), SubmitTask("0001", "task-0001")) val a = arr(1) println(a)
a match { case SubmitTask(id, name) => { println(s"$id, $name") } case HeartBeat(time) => { println(time) } case CheckTimeOutTask => { println("check") } } } 五:Scala的高級內容:泛型 (*)泛型類 泛型類(類聲明時類名後面括號中即爲類型參數),顧名思義,其實就是在類的聲明中,定義一些泛型類型,然後在類內部,比如field、method,就可以使用這些泛型類型 使用泛型類,通常需要對類中某些成員,比如某些field或者method的參數或變量,進行統一的類型限制,這樣可以保證程序更好健壯性和穩定性 如果不適用泛型進行統一的類型限制,那麼在後面程序運行中,難免會出現問題,比如傳入了不希望出現的類型,導致程序崩潰 class GenericClass1 { private var content:Int = 100
//定義set和get方法 def setContent(value:Int): Unit ={ content = value }
def getContent():Int= { content } } class GenericClass2{ private var content:String = "abc"
//定義set和get方法 def setContent(value:String): Unit ={ content = value }
def getContent():String= { content } }
/** * 定義一個泛型類 * @tparam T */ class GenericClass[T] { private var content: T = _
//定義set和get方法 def setContent(value: T): Unit = { content = value }
def getContent(): T = { content } } object GenericClassTest{ def main(args: Array[String]): Unit = { val intObj = new GenericClass1 intObj.setContent(1234) intObj.getContent()
val strObj = new GenericClass2 strObj.setContent("abc") strObj.getContent()
val genObj = new GenericClass[String] genObj.setContent("abc") println(genObj.getContent())
val genObj2 = new GenericClass[Int] genObj2.setContent(123) println(genObj2.getContent())
val genObj3 = new GenericClass[Double] genObj3.setContent(12.3) println(genObj3.getContent()) } } (*)泛型函數 泛型函數(方法聲明時方法名後面括號中的類型參數),與泛型類類似,可以給某個函數在聲明時指定泛型類型,然後在函數體內,多個變量或者返回值 引用反射包 import scala.reflect.ClassTag ClassTag:表示scala在運行時的狀態信息,這裏表示調用時的數據類型 /** * 泛型函數 * Created by zhangjingcun on 2018/9/14 14:47. */ object GenericFunc {
/** * 定義函數,創建一個int類型的數組 * @param elem * @return */ def mkIntArray(elem: Int*) = Array[Int](elem: _*)
/** * 定義函數:創建一個String類型的函數 * @param elem * @return */ def mkStringArray(elem:String*) =Array[String](elem:_*)
/** * 在函數中使用泛型,取代上面的兩個函數 * @param elem * @tparam T * @return */ def mkArray[T:ClassTag](elem:T*) = Array[T](elem:_*)
/** * 定義泛型函數 * @param name * @param age * @tparam T1 * @tparam T2 * @tparam T3 * @return */ def say[T1, T2, T3](name:T1, age:T2):T3 = { (s"name:${name}, age:${age}").asInstanceOf[T3] } def main(args: Array[String]): Unit = { println(mkIntArray(1, 2, 3, 4, 5).toBuffer)
println(mkStringArray("a", "b", "c").toBuffer)
println("*****************調用泛型函數**************************************") println(mkArray(1, 2, 3, 4, 5).toBuffer)
println(mkArray("a", "b", "c").toBuffer)
println("*****************調用泛型函數2**************************************") println(say[String,Int,String]("Tom", 23)) println(say("Mike", 23)) //scala會自動推斷泛型的實際類型 } }
(*)泛型的上界、泛型的下界 核心意思:泛型的取值範圍 1:以普通的數據類型爲例 10 < i:Int < 100 ->規定了i的取值範圍(10-100) 2:規定:類型的取值範圍-> 上界、下界 定義幾個類:(具有繼承關係) Class A -> Class B -> Class C -> Class D 定義泛型: D <: T 泛型類型 <: B --------> 泛型T的取值範圍:B、C、D 3:概念: 上界: 定義 S <: T 這是類型上界的定義,也就是S必須是類型T的子類(或者本身,自己也可以認爲自己是自己的子類) (upper bound )等同於 java中的<T extends Comparable<T>> 下界: 定義 U >: T 這是類型下界的定義,也就是U必須是類型T的父類(或者本身,自己也可以認爲自己是自己的父類) (lower bound) 等同於 java中的<T super Comparable<T>> 4:舉例: 上界:參考UpperBound.scala 5:舉例: 拼接字符串的例子,接收類型必須是String或者String的子類 scala> def addTwoString[T <: String](x:T, y:T) = println(x + "********" + y) addTwoString: [T <: String](x: T, y: T)Unit scala> addTwoString(1233, 1234) <console>:13: error: inferred type arguments [Any] do not conform to method addTwoString's type parameter bounds [T <: String] addTwoString("adc", 1234) (*)視圖界定:核心擴展了上界和下界的範圍,表達方式,上界爲例 <% 1)可以接收以下類型 (1)上界和下界的類型 (2)允許通過隱式轉換過去的類型(定義一個隱式轉換函數) 實際上我們希望 addTwoString(1233, 1234) 1.首先將1233轉換成字符串的1233 2.再拼加,得到我們想要的結果(由此引出新的概念,視圖界定) 2)改寫上面的例子 (1)定義一個轉換規則:即隱式轉換函數 implicit def int2String(x:Int):String = { x.toString() } (2)使用視圖界定改寫函數addTwoString def addTwoString[T <% String](x:T, y:T) = println(x + "********" + y) 表示:T可以是String和String的子類,也可以是能夠轉換成String的其他類 調用:報錯,沒有找到隱式轉換規則吧int轉換成String類型 scala> def addTwoString[T <% String](x:T, y:T) = println(x + "********" + y) addTwoString: [T](x: T, y: T)(implicit evidence$1: T => String)Unit
scala> addTwoString(100, 200) <console>:13: error: No implicit view available from Int => String. addTwoString(100, 200) ^ 調用:正常了 scala> implicit def int2String(x:Int):String = { x.toString() } warning: there was one feature warning; re-run with -feature for details int2String: (x: Int)String
scala> addTwoString(100, 200) 100********200 (3)分析執行過程:addTwoString(100, 200) (1)檢查參數的類型,Int類型,接收的是String類型 (2)在當前的會話中查找有沒有一個隱式轉換函數,滿足Int可以轉換成String類型 (3)如果找到了,先調用這個隱式轉換函數 如果沒有找到,出錯 (4)在調用函數 (4)在測試一下 #還是報錯,這裏提示了我們找不到any類型轉換成String的隱式轉換函數 scala> addTwoString(1234, "dd") <console>:14: error: No implicit view available from Any => String. addTwoString(1234, "dd") ^
#定義一個any類型轉換爲String類型的隱式轉換函數 scala> implicit def any2String(x:Any):String = (x.toString()) warning: there was one feature warning; re-run with -feature for details any2String: (x: Any)String
scala> addTwoString(1234, "dd") 1234********dd (*)隱式轉換函數 /** * 定義一個水果類 * @param name */ class Fruit(name:String){ def getName() ={ name } }
/** * 猴子類,喜歡吃水果 * @param f */ class Monkey(f:Fruit){ def say() = println(s"Monkey like ${f.getName()}") }
object ImplicitDemo {
/** * 定義一個轉換規則(隱式轉換函數),把Fruit轉換成Monkey * @param f * @return */ implicit def Fruit2Monkey(f:Fruit):Monkey = { new Monkey(f) } def main(args: Array[String]): Unit = { //定義一個水果對象 val f: Fruit = new Fruit("桃子") //調用這個函數 f.say() } }
(*)斜變和逆變 (1)scala的協變和逆變是非常特色的功能,完全解決了java泛型的一大缺憾 //舉例來說: Java中,如果有Bird類是Animal的子類,那麼EatSomething[Bird]是不是EatSomething[Animal]的子類?答案是:不行,因此對於開發程序造成了很多的麻煩 //在scala中,只要靈活的使用協變和逆變,就可以解決Java泛型的問題 1:協變的概念:(泛型變量的值可以是本身或者其子類的類型)scala的類或者特徵的泛型定義中,如果在類型參數前面加入+符號,就可以使類或者特徵變成協變了 參考CovarianceDamo代碼 2:逆變的概念:(泛型變量的值可以是本身或者其父類的類型)在類或者特徵的定義中,在類型參數之前加上一個-符號,就可以定義逆變泛型類和特徵了 參考ContravanceDemo代碼 (*)隱式參數 核心:隱式轉換 參考ImplicitParam代碼 (*)隱式類 在類前面加個implicit關鍵字,變成了隱式轉換類 參考ImplicitClassDemo代碼 (*)使用柯里化實現隱式轉換
(actor編程,兩年前已經被廢棄)