Scala與Java的關係... java
安裝Scala算法
Scala解釋器的使用spring
聲明變量編程
數據類型與操做符windows
函數調用與apply()函數設計模式
if表達式api
輸入和輸出安全
循環網絡
Scala與Java的關係是很是緊密的!!
由於Scala是基於Java虛擬機,也就是JVM的一門編程語言。全部Scala的代碼,都須要通過編譯爲字節碼,而後交由Java虛擬機來運行。
因此Scala和Java是能夠無縫互操做的。Scala能夠任意調用Java的代碼。因此Scala與Java的關係是很是很是緊密的。
·從Scala官方網站下載,http://www.scala-lang.org/download/,windows版本的安裝包是scala-2.11.7.msi。
·使用下載下來的安裝包安裝Scala。
·在PATH環境變量中,配置$SCALA_HOME/bin目錄。
·在windows命令行內便可直接鍵入scala,打開scala命令行,進行scala編程。
·REPL:Read(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循環)。scala解釋器也被稱爲REPL,會快速編譯scala代碼爲字節碼,而後交給JVM來執行。
·計算表達式:在scala>命令行內,鍵入scala代碼,解釋器會直接返回結果給你。若是你沒有指定變量來存放這個值,那麼值默認的名稱爲res,並且會顯示結果的數據類型,好比Int、Double、String等等。
·例如,輸入1 + 1,會看到res0: Int = 2
·內置變量:在後面能夠繼續使用res這個變量,以及它存放的值。
·例如,2.0 * res0,返回res1: Double = 4.0
·例如,"Hi, " + res0,返回res2: String = Hi, 2
·自動補全:在scala>命令行內,可使用Tab鍵進行自動補全。
·例如,輸入res2.to,敲擊Tab鍵,解釋器會顯示出如下選項,toCharArray,toLowerCase,toString,toUpperCase。由於此時沒法斷定你須要補全的是哪個,所以會提供給你全部的選項。
·例如,輸入res2.toU,敲擊Tab鍵,直接會給你補全爲res2.toUpperCase。
·聲明val變量:能夠聲明val變量來存放表達式的計算結果。
·例如,val result = 1 + 1
·後續這些常量是能夠繼續使用的,例如,2 * result
·可是常量聲明後,是沒法改變它的值的,例如,result = 1,會返回error: reassignment to val的錯誤信息。
·聲明var變量:若是要聲明值能夠改變的引用,可使用var變量。
·例如,val myresult = 1,myresult = 2
·可是在scala程序中,一般建議使用val,也就是常量,所以好比相似於spark的大型複雜系統中,須要大量的網絡傳輸數據,若是使用var,可能會擔憂值被錯誤的更改。
·在Java的大型複雜系統的設計和開發中,也使用了相似的特性,咱們一般會將傳遞給其餘模塊 / 組件 / 服務的對象,設計成不可變類(Immutable Class)。在裏面也會使用java的常量定義,好比final,阻止變量的值被改變。從而提升系統的健壯性(robust,魯棒性),和安全性。
·指定類型:不管聲明val變量,仍是聲明var變量,均可以手動指定其類型,若是不指定的話,scala會自動根據值,進行類型的推斷。
·例如,val name: String = null
·例如,val name: Any = "leo"
·聲明多個變量:能夠將多個變量放在一塊兒進行聲明。
·例如,val name1, name2:String = null
·例如,val num1, num2 = 100
·基本數據類型:Byte、Char、Short、Int、Long、Float、Double、Boolean。
·乍一看與Java的基本數據類型的包裝類型相同,可是scala沒有基本數據類型與包裝類型的概念,統一都是類。scala本身會負責基本數據類型和引用類型的轉換操做。
·使用以上類型,直接就能夠調用大量的函數,例如,1.toString(),1.to(10)。
·類型的增強版類型:scala使用不少增強類給數據類型增長了上百種加強的功能或函數。
·例如,String類經過StringOps類加強了大量的函數,"Hello".intersect(" World")。
·例如,Scala還提供了RichInt、RichDouble、RichChar等類型,RichInt就提供了to函數,1.to(10),此處Int先隱式轉換爲RichInt,而後再調用其to函數
·基本操做符:scala的算術操做符與java的算術操做符也沒有什麼區別,好比+、-、*、/、%等,以及&、|、^、>>、<<等。
·可是,在scala中,這些操做符實際上是數據類型的函數,好比1 + 1,能夠寫作1.+(1)
·例如,1.to(10),又能夠寫作1 to 10
·scala中沒有提供++、--操做符,咱們只能使用+和-,好比counter = 1,counter++是錯誤的,必須寫作counter += 1.
·函數調用方式:在scala中,函數調用也很簡單。
·例如,import scala.math._,sqrt(2),pow(2, 4),min(3, Pi)。
·不一樣的一點是,若是調用函數時,不須要傳遞參數,則scala容許調用函數時省略括號的,例如,"Hello World".distinct
·apply函數
·Scala中的apply函數是很是特殊的一種函數,在Scala的object中,能夠聲明apply函數。而使用「類名()」(嚴格來說應該是「對象名()」)的形式,其實就是「類名.apply()」(嚴格來說應該是「對象名.apply()」)的一種縮寫。一般使用這種方式來構造類的對象,而不是使用「new 類名()」的方式。
·例如,"Hello World"(6),由於在StringOps類中有def apply(n: Int): Char的函數定義,因此"Hello World"(6),其實是"Hello World".apply(6)的縮寫。
·例如,Array(1, 2, 3, 4),其實是用Array object的apply()函數來建立Array類的實例,也就是一個數組。
·if表達式的定義:在Scala中,if表達式是有值的,就是if或者else中最後一行語句返回的值。
·例如,val age = 30; if (age > 18) 1 else 0
·能夠將if表達式賦予一個變量,例如,val isAdult = if (age > 18) 1 else 0
·另一種寫法,var isAdult = -1; if(age > 18) isAdult = 1 else isAdult = 0,可是一般使用上一種寫法
·if表達式的類型推斷:因爲if表達式是有值的,而if和else子句的值類型可能不一樣,此時if表達式的值是什麼類型呢?Scala會自動進行推斷,取兩個類型的公共父類型。
·例如,if(age > 18) 1 else 0,表達式的類型是Int,由於1和0都是Int
·例如,if(age > 18) "adult" else 0,此時if和else的值分別是String和Int,則表達式的值是Any,Any是String和Int的公共父類型
·若是if後面沒有跟else,則默認else的值是Unit,也用()表示,相似於java中的void或者null。例如,val age = 12; if(age > 18) "adult"。此時就至關於if(age > 18) "adult" else ()。
·將if語句放在多行中:默認狀況下,REPL只能解釋一行語句,可是if表達式一般須要放在多行。
·可使用{}的方式,好比如下方式,或者使用:paste和ctrl+D的方式。
if(age > 18) { "adult"
} else if(age > 12) "teenager" else "children"
·默認狀況下,scala不須要語句終結符,默認將每一行做爲一個語句
·一行放多條語句:若是一行要放多條語句,則必須使用語句終結符
·例如,使用分號做爲語句終結符,var a, b, c = 0; if(a < 10) { b = b + 1; c = c + 1 }
·一般來講,對於多行語句,仍是會使用花括號的方式
if(a < 10) {
b = b + 1
c = c + 1
}
·塊表達式:塊表達式,指的就是{}中的值,其中能夠包含多條語句,最後一個語句的值就是塊表達式的返回值。
·例如,var d = if(a < 10) { b = b + 1; c + 1 }
·print和println:print打印時不會加換行符,而println打印時會加一個換行符。
·例如,print("Hello World"); println("Hello World")
·printf:printf能夠用於進行格式化
·例如,printf("Hi, my name is %s, I'm %d years old.\n", "Leo", 30)
·readLine: readLine容許咱們從控制檯讀取用戶輸入的數據,相似於java中的System.in和Scanner的做用。
·綜合案例:遊戲廳門禁
val name = readLine("Welcome to Game House. Please tell me your name: ")
print("Thanks. Then please tell me your age: ")
val age = readInt()
if(age > 18) {
printf("Hi, %s, you are %d years old, so you are legel to come here!", name, age)
} else {
printf("Sorry, boy, %s, you are only %d years old. you are illegal to come here!", name, age)
}
·while do循環:Scala有while do循環,基本語義與Java相同。
var n = 10
while(n > 0) {
println(n)
n -= 1
}
·Scala沒有for循環,只能使用while替代for循環,或者使用簡易版的for語句
·簡易版for語句:var n = 10; for(i <- 1 to n) println(i)
·或者使用until,表式不達到上限:for(i <- 1 until n) println(i)
·也能夠對字符串進行遍歷,相似於java的加強for循環,for(c <- "Hello World") print(c)
·跳出循環語句
·scala沒有提供相似於java的break語句。
·可是可使用boolean類型變量、return或者Breaks的break函數來替代使用。
import scala.util.control.Breaks._
breakable {
var n = 10
for(c <- "Hello World") {
if(n == 5) break;
print(c)
n -= 1
}
}
·多重for循環:九九乘法表
for(i <- 1 to 9; j <- 1 to 9) {
if(j == 9) {
println(i * j)
} else {
print(i * j + " ")
}
}
·if守衛:取偶數
for(i <- 1 to 100 if i % 2 == 0) println(i)
·for推導式:構造集合
for(i <- 1 to 10) yield i
在Scala中定義函數時,須要定義函數的函數名、參數、函數體。
咱們的第一個函數以下所示:
def sayHello(name: String, age: Int) = {
if (age > 18) { printf("hi %s, you are a big boy\n", name); age }
else { printf("hi %s, you are a little boy\n", name); age
}
sayHello("leo", 30)
Scala要求必須給出全部參數的類型,可是不必定給出函數返回值的類型,只要右側的函數體中不包含遞歸的語句,Scala就能夠本身根據右側的表達式推斷出返回類型。
單行的函數:def sayHello(name: String) = print("Hello, " + name)
若是函數體中有多行代碼,則可使用代碼塊的方式包裹多行代碼,代碼塊中最後一行的返回值就是整個函數的返回值。與Java中不一樣,不是使用return返回值的。
好比以下的函數,實現累加的功能:
def sum(n: Int) = {
var sum = 0;
for(i <- 1 to n) sum += i
sum
}
若是在函數體內遞歸調用函數自身,則必須手動給出函數的返回類型。
例如,實現經典的斐波那契數列:
9 + 8; 8 + 7 + 7 + 6; 7 + 6 + 6 + 5 + 6 + 5 + 5 + 4; ....
def fab(n: Int): Int = {
if(n <= 1) 1
else fab(n - 1) + fab(n - 2)
}
在Scala中,有時咱們調用某些函數時,不但願給出參數的具體值,而但願使用參數自身默認的值,此時就定義在定義函數時使用默認參數。
def sayHello(firstName: String, middleName: String = "William", lastName: String = "Croft") = firstName + " " + middleName + " " + lastName
若是給出的參數不夠,則會從左往右依次應用默認參數。
def sayHello(name: String, age: Int = 20) {
print("Hello, " + name + ", your age is " + age)
}
sayHello("leo")
在調用函數時,也能夠不按照函數定義的參數順序來傳遞參數,而是使用帶名參數的方式來傳遞。
sayHello(firstName = "Mick", lastName = "Nina", middleName = "Jack")
還能夠混合使用未命名參數和帶名參數,可是未命名參數必須排在帶名參數前面。
sayHello("Mick", lastName = "Nina", middleName = "Jack")
在Scala中,有時咱們須要將函數定義爲參數個數可變的形式,則此時可使用變長參數定義函數。
def sum(nums: Int*) = {
var res = 0
for (num <- nums) res += num
res
}
sum(1, 2, 3, 4, 5)
在若是想要將一個已有的序列直接調用變長參數函數,是不對的。好比val s = sum(1 to 5)。此時須要使用Scala特殊的語法將參數定義爲序列,讓Scala解釋器可以識別。這種語法很是有用!必定要好好主意,在spark的源碼中大量地使用到了。
val s = sum(1 to 5: _*)
案例:使用遞歸函數實現累加
def sum2(nums: Int*): Int = {
if (nums.length == 0) 0
else nums.head + sum2(nums.tail: _*)
}
在Scala中,定義函數時,若是函數體直接包裹在了花括號裏面,而沒有使用=鏈接,則函數的返回值類型就是Unit。這樣的函數就被稱之爲過程,即過程就是沒有返回值的函數。
過程還有一種寫法,就是將函數的返回值類型定義爲Unit。
def sayHello(name: String) = "Hello, " + name//函數
def sayHello(name: String) { print("Hello, " + name); "Hello, " + name }//有值,但未使用=號,仍是過程
def sayHello(name: String): Unit = "Hello, " + name//有值,有=號,但強制返回類型爲空,則仍是過程
在Scala中,提供了lazy值的特性,也就是說,若是將一個變量聲明爲lazy,則只有在第一次使用該變量時,變量對應的表達式纔會發生計算。這種特性對於特別耗時的計算操做特別有用,好比打開文件進行IO,進行網絡IO等。
import scala.io.Source._
lazy val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString
即便文件不存在,也不會報錯,只有第一個使用變量時會報錯,證實了表達式計算的lazy特性。
val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString
lazy val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString
至關於定義了一個方法,只有在調用該方法時纔會去執行方法體:
def lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString
在Scala中,異常處理和捕獲機制與Java是很是類似的。
try {
throw new IllegalArgumentException("x should not be negative")
} catch {
case _: IllegalArgumentException => println("Illegal Argument!")
} finally {
print("release resources!")
}
Import java.io._
try {
throw new IOException(「io exception!!!")
} catch {
case _: IllegalArgumentException => println("illegal argument")
}
try {
throw new IOException("user defined exception")
} catch {
case e1: IllegalArgumentException => println("illegal argument")
case e2: IOException => println("io exception")
}
在Scala中,Array表明的含義與Java中相似,也是長度不可改變的數組。此外,因爲Scala與Java都是運行在JVM中,雙方能夠互相調用,所以Scala數組的底層其實是Java數組。例如字符串數組在底層就是Java的String[],整數數組在底層就是Java的Int[]。
// 數組初始化後,長度就固定下來了,並且元素所有根據其類型初始化
val a = new Array[Int](10)
a(0)
a(0) = 1
val a = new Array[String](10)
// 能夠直接使用Array()建立數組,元素類型自動推斷
val a = Array("hello", "world")
a(0) = "hi"
val a = Array("leo", 30)
在Scala中,若是須要相似於Java中的ArrayList這種長度可變的集合類,則可使用ArrayBuffer。
// 若是不想每次都使用全限定名,則能夠預先導入ArrayBuffer類
import scala.collection.mutable.ArrayBuffer
// 使用ArrayBuffer()的方式能夠建立一個空的ArrayBuffer
val b = ArrayBuffer[Int]()
// 使用+=操做符,能夠添加一個元素,或者多個元素
// 這個語法必需要謹記在心!由於spark源碼裏大量使用了這種集合操做語法!
b += 1
b += (2, 3, 4, 5)
// 使用++=操做符,能夠添加其餘集合中的全部元素
b ++= Array(6, 7, 8, 9, 10)
// 使用trimEnd()函數,能夠從尾部截斷指定個數的元素
b.trimEnd(5)
// 使用insert()函數能夠在指定位置插入元素
// 可是這種操做效率很低,由於須要移動指定位置後的全部元素
b.insert(5, 6)
b.insert(6, 7, 8, 9, 10)
// 使用remove()函數能夠移除指定位置的元素
b.remove(1)
b.remove(1, 3)
// Array與ArrayBuffer能夠互相進行轉換
b.toArray
a.toBuffer
// 使用for循環和until遍歷Array / ArrayBuffer
// 使until是RichInt提供的函數
for (i <- 0 until b.length)
println(b(i))
// 跳躍遍歷Array / ArrayBuffer
for(i <- 0 until (b.length, 2))
println(b(i))
// 從尾部遍歷Array / ArrayBuffer
for(i <- (0 until b.length).reverse)
println(b(i))
// 使用「加強for循環」遍歷Array / ArrayBuffer
for (e <- b)
println(e)
// 數組元素求和
val a = Array(1, 2, 3, 4, 5)
val sum = a.sum
// 獲取數組最大值
val max = a.max
// 對數組進行排序
scala.util.Sorting.quickSort(a)
// 獲取數組中全部元素內容
a.mkString
a.mkString(", ")
a.mkString("<", ",", ">")
// toString函數
a.toString
b.toString
// 對Array進行轉換,獲取的仍是Array
val a = Array(1, 2, 3, 4, 5)
val a2 = for (ele <- a) yield ele * ele
// 對ArrayBuffer進行轉換,獲取的仍是ArrayBuffer
val b = ArrayBuffer[Int]()
b += (1, 2, 3, 4, 5)
val b2 = for (ele <- b) yield ele * ele
// 結合if守衛,僅轉換須要的元素
val a3 = for (ele <- if ele % 2 == 0) yield ele * ele
// 使用函數式編程轉換數組(一般使用第一種方式)
a.filter(_ % 2 == 0).map(2 * _)
a.filter { _ % 2 == 0 } map { 2 * _ }
// 構建數組
val a = ArrayBuffer[Int]()
a += (1, 2, 3, 4, 5, -1, -3, -5, -9)
// 每發現一個第一個負數以後的負數,就進行移除,性能較差,屢次移動數組
var foundFirstNegative = false
var arrayLength = a.length
var index = 0
while (index < arrayLength) {
if (a(index) >= 0) {
index += 1
} else {
if (!foundFirstNegative) { foundFirstNegative = true; index += 1 }
else { a.remove(index); arrayLength -= 1 }
}
}
// 從新構建數組
val a = ArrayBuffer[Int]()
a += (1, 2, 3, 4, 5, -1, -3, -5, -9)
// 每記錄全部不須要移除的元素的索引,稍後一次性移除全部須要移除的元素
// 性能較高,數組內的元素遷移只要執行一次便可
var foundFirstNegative = false
val keepIndexes = for (i <- 0 until a.length if !foundFirstNegative || a(i) >= 0) yield {
if (a(i) < 0) foundFirstNegative = true
i
}
for (i <- 0 until keepIndexes.length) { a(i) = a(keepIndexes(i)) }
a.trimEnd(a.length - keepIndexes.length)
// 建立一個不可變的Map
val ages = Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23)
ages("Leo") = 31
// 建立一個可變的Map
val ages = scala.collection.mutable.Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23)
ages("Leo") = 31
// 使用另一種方式定義Map元素
val ages = Map(("Leo", 30), ("Jen", 25), ("Jack", 23))
// 建立一個空的HashMap
val ages = new scala.collection.mutable.HashMap[String, Int]
// 獲取指定key對應的value,若是key不存在,會報錯
val leoAge = ages("Leo")
val leoAge = ages("leo")
// 使用contains函數檢查key是否存在
val leoAge = if (ages.contains("leo")) ages("leo") else 0
// getOrElse函數
val leoAge = ages.getOrElse("leo", 0)
// 更新Map的元素
ages("Leo") = 31
// 增長多個元素
ages += ("Mike" -> 35, "Tom" -> 40)
// 移除元素
ages -= "Mike"
// 更新不可變的map
val ages2 = ages + ("Mike" -> 36, "Tom" -> 40)
// 移除不可變map的元素
val ages3 = ages - "Tom"
// 遍歷map的entrySet
for ((key, value) <- ages) println(key + " " + value)
// 遍歷map的key
for (key <- ages.keySet) println(key)
// 遍歷map的value
for (value <- ages.values) println(value)
// 生成新map,反轉key和value
for ((key, value) <- ages) yield (value, key)
// SortedMap能夠自動對Map的key的排序
val ages = scala.collection.immutable.SortedMap("leo" -> 30, "alice" -> 15, "jen" -> 25)
// LinkedHashMap能夠記住插入entry的順序
val ages = new scala.collection.mutable.LinkedHashMap[String, Int]
ages("leo") = 30
ages("alice") = 15
ages("jen") = 25
// 簡單Tuple
val t = ("leo", 30)
// 訪問Tuple
t._1
// zip操做
val names = Array("leo", "jack", "mike")
val ages = Array(30, 24, 26)
val nameAges = names.zip(ages)
for ((name, age) <- nameAges) println(name + ": " + age)
// 定義類,包含field以及方法
class HelloWorld {
private var name = "leo"
def sayHello() { print("Hello, " + name) }
def getName = name
}
// 建立類的對象,並調用其方法
val helloWorld = new HelloWorld
helloWorld.sayHello()
print(helloWorld.getName) // 也能夠不加括號,若是定義方法時不帶括號,則調用方法時也不能帶括號
1、定義不帶private的 var field,此時scala生成class時,會自動生成一個private[this]的成員字段(名稱與field不一樣),並還生成一對getter和setter方法,分別叫作field和 field_=,而且getter和setter方法的訪問修飾符與field定義相同
2、而若是使用private修飾field,則只生成的getter和setter,且訪問修飾也是private的
3、若是定義val field,則只會生成getter方法
4、 若是不但願生成setter和getter方法,則將field聲明爲private[this]
class Student {
var name = "leo"
}
// 調用getter和setter方法,分別叫作name和name_=
val leo = new Student
print(leo.name)
leo.name = "leo1" //實際上會調用 leo.name_=("leo1")方法
// 若是隻是但願擁有簡單的getter和setter方法,那麼就按照scala提供的語法規則,根據需求爲field選擇合適的修飾符就好:var、val、private、private[this]
// 可是若是但願可以本身對getter與setter進行控制,則能夠自定義getter與setter方法
// 自定義setter方法的時候必定要注意scala的語法限制,簽名、=、參數間不能有空格
class Student {
private var myName = "leo" //默認會生成一對private getter(myName)、setter(myName _=)方法
def name = "your name is " + myName //自定義myName 成員變量getter方法
def name_=(newValue: String) {//自定義myName 成員變量的setter方法
print("you cannot edit your name!!!")
}
}
val leo = new Student
print(leo.name)
leo.name = "leo1" //會去調用 name_+ 自定義setter 方法
// 若是你不但願field有setter方法,則能夠定義爲val,可是此時就不再能更改field的值了
// 可是若是但願可以僅僅暴露出一個getter方法,而且還能經過某些方法更改field的值,那麼須要綜合使用private以及自定義getter方法。此時,因爲field是private的,因此setter和getter都是private,對外界沒有暴露;本身能夠實現修改field值的方法;本身能夠覆蓋getter方法
class Student {
private var myName = "leo"
def updateName(newName: String) { //更改field的其餘方法(命名約束的不知足setter方法)
if(newName == "leo1") myName = newName
else print("not accept this new name!!!")
}
def name = "your name is" + myName //覆蓋自動生成的私有getter方法
}
// 若是將field使用private來修飾,那麼表明這個field是類私有的,在類的方法中,能夠直接訪問類的其餘對象的private field
// 這種狀況下,若是不但願field被其餘對象訪問到,那麼可使用private[this],意味着對象私有的field,只有本對象內能夠訪問到(子類對象中也是不能夠訪問的,由於私有的是不能被繼承的)
class Student {
private var myAge = 0 //試着修改爲private[this]
def age_=(newValue: Int) {
if (newValue > 0) myAge = newValue
else print("illegal age!")
}
def age = myAge
def older(s: Student) = {
myAge > s.myAge //修改爲private[this]後,就會報錯
}
}
private[this]還能夠用爲修改方法
// Scala的getter和setter方法的命名與java是不一樣的,是field和field_=的方式
// 若是要讓scala自動生成java風格的getter和setter方法,只要給field添加@BeanProperty註解便可
// 此時會生成4個方法,name: String、name_=(newValue: String): Unit、getName(): String、setName(newValue: String): Unit
import scala.reflect.BeanProperty
class Student {
@BeanProperty var name: String = _
}
class Student(@BeanProperty var name: String)
val s = new Student
s.setName("leo")
s.getName()
// Scala中,能夠給類定義多個輔助constructor,相似於java中的構造函數重載
// 輔助constructor之間能夠互相調用,並且必須第一行調用主constructor或其餘輔構造器
class Student {
private var name = ""
private var age = 0
def this(name: String) {
this()
this.name = name
}
def this(name: String, age: Int) {
this(name)
this.age = age
}
}
// Scala中,主constructor是與類名放在一塊兒的,有且只有一個,與java不一樣
// 並且類中,沒有定義在任何方法中的代碼(包括成員字段),都屬於主constructor的代碼,且執行的順序與代碼書寫的順序一致。這其實與Java是同樣的,在Java中方法以外的代碼(成員以及代碼塊)會在構造器調用以前最早執行,姑且將這些代碼看做也是放到了一個主構造器中進行執行的,只不過這種主構造器不能帶構造參數
//主構造器與類定義是在一塊兒的,若是有參數,則在類名後面跟括號便可:
class Student(val name: String, val age: Int) {
println("your name is " + name + ", your age is " + age)
}
固然沒有參數的主構造器也能夠帶括號:
class Student() {}
// 主constructor中還能夠經過使用默認參數,來給參數默認的值
class Student(val name: String = "leo", val age: Int = 30) {
println("your name is " + name + ", your age is " + age)
}
// 若是主constrcutor傳入的參數什麼修飾都沒有,好比name: String,那麼若是類內部除主constrcutor方法外其它方法也使用到了,則會自動將該參數修飾爲private[this] name以便其它方法使用:
class Student(name: String) {
def f(){print(name)}
def f(s:Student){print(s.name)}//編譯出錯
}
class Student(val name: String) {
def f(){print(name)}
def f(s:Student){print(s.name)}//編譯正確,證實沒有使用var或val修飾時,且在除主構造器中使用外,則使用private[this]來修飾
}
類中沒有定義的在任何方法中的代碼都屬於主構造器,而且執行順序與書寫順序一致:
// Scala中,一樣能夠在類中定義內部類;可是與java不一樣的是,每一個外部類的對象的內部類,都屬於不一樣的類
import scala.collection.mutable.ArrayBuffer
class Class {
class Student(val name: String) {}
val students = new ArrayBuffer[Student]
def getStudent(name: String) = {
new Student(name)
}
}
val c1 = new Class
val s1 = c1.getStudent("leo")
c1.students += s1
val c2 = new Class
val s2 = c2.getStudent("leo")
c1.students += s2 //異常
1、object,至關於class的單個實例(但與從class實例化出來的對象的內容決不同),一般在裏面放一些class層面上共享的內容,如Java中的靜態field或者method即在定義在object中(注:Scala中沒有Java的靜態概念,因此延伸出了object這個東東)
2、你能夠將object看做是一個類class,只是這個類在內存中只有一個單例,且定義的object名就是實例名,不需咱們本身實例化,運行時JVM已幫咱們new出來了
3、第一次調用object的方法時,就會執行object的constructor,也就是object內部不在method中的代碼;可是object不能定義接受參數的constructor
4、注意,object的constructor只會在其第一次被調用時執行一次,之後再次調用就不會再次執行constructor了
5、object一般用於做爲單例模式的實現,或者放class的靜態成員,好比工具方法
object Person {
private var eyeNum = 2
println("this Person object!")
def getEyeNum = eyeNum
}
// 若是有一個class,還有一個與class同名的object,那麼就稱這個object是class的伴生對象,class是object的伴生類
// 伴生類和伴生對象必須存放在一個.scala文件之中
// 伴生類和伴生對象,最大的特色就在於,互相能夠訪問private field
object Person {
private val eyeNum = 2
def getEyeNum = eyeNum
}
class Person(val name: String, val age: Int) {
def sayHello = println("Hi, " + name + ", I guess you are " + age + " years old!" + ", and usually you must have " + Person.eyeNum + " eyes.")
}
// object的功能其實和class相似,除了不能定義接受參數的constructor以外
// object也能夠繼承抽象類,並覆蓋抽象類中的方法
abstract class Hello(var message: String) {
def sayHello(name: String): Unit
}
object HelloImpl extends Hello("hello") {
override def sayHello(name: String) = {
println(message + ", " + name)
}
}
// object中很是重要的一個特殊方法,就是apply方法
// 一般在伴生對象中實現apply方法,並在其中實現構造伴生類的對象的功能,通常用做工廠方法
// 而建立伴生類的對象時,一般不會使用new Class的方式,而是使用Class()的方式,隱式地調用伴生對象得apply方法,這樣會讓對象建立更加簡潔
// 好比,Array類的伴生對象的apply方法就實現了接收可變數量的參數,並建立一個Array對象的功能
val a = Array(1, 2, 3, 4, 5)
// 好比,定義本身的伴生類和伴生對象
class Person(val name: String)
object Person {
def apply(name: String) = new Person(name)
}
另外,若是直接在一個對象後面接小括號,則會去調用這個對象所對應類中相應的apply方法:
class Person {
def apply(name: String) = println(name)
}
scala> val p = new Person
scala> p("Persion")
Persion
// 就如同java中,若是要運行一個程序,必須編寫一個包含main方法類同樣;在scala中,若是要運行一個應用程序,那麼必須有一個main方法,做爲入口
// scala中的main方法定義爲def main(args: Array[String]),並且必須定義在object中
object HelloWorld {
def main(args: Array[String]) {
println("Hello World!!!")
}
}
// 除了本身實現main方法以外,還能夠繼承App Trait,而後將須要在main方法中運行的代碼,直接做爲object的constructor代碼;並且用args能夠接受傳入的參數
object HelloWorld extends App {
if (args.length > 0) println("hello, " + args(0))
else println("Hello World!!!")
}
// 若是要運行上述代碼,須要將其放入.scala文件,而後先使用scalac編譯,再用scala執行
scalac HelloWorld.scala
scala -Dscala.time HelloWorld
// App Trait的工做原理爲:App Trait繼承自DelayedInit Trait,scalac命令進行編譯時,會把繼承App Trait的object的constructor代碼都放到DelayedInit Trait的delayedInit方法中,而後由App Trait的main方法去調用執行
// Scala沒有直接提供相似於Java中的Enum這樣的枚舉特性,若是要實現枚舉,則須要用object繼承Enumeration類,而且調用Value方法來初始化枚舉值
object Season extends Enumeration {
val SPRING, SUMMER, AUTUMN, WINTER = Value
}
// 還能夠經過Value傳入枚舉值的id和name,經過id和toString能夠獲取; 還能夠經過id和name來查找枚舉值
object Season extends Enumeration {
val SPRING = Value(0, "spring")
val SUMMER = Value(1, "summer")
val AUTUMN = Value(2, "autumn")
val WINTER = Value(3, "winter")
}
Season(0) // spring
Season.withName("spring") // spring,根據名稱找
// 使用枚舉object.values能夠遍歷枚舉值
for (ele <- Season.values) println(ele)
// Scala中,讓子類繼承父類,與Java同樣,也是使用extends關鍵字
// 繼承就表明,子類能夠從父類繼承父類的field和method;而後子類能夠在本身內部放入父類所沒有,子類特有的field和method;使用繼承能夠有效複用代碼
// 子類能夠覆蓋父類的field和method;但要注意的是final類是不能被繼承的,並且final類型的field和method是沒法被覆蓋的
class Person {
private var name = "leo"
def getName = name
}
class Student extends Person {
private var score = "A"
def getScore = score
}
// Scala中,若是子類要覆蓋一個父類中的非抽象方法,則必須使用override關鍵字;若是是抽象的方法,則能夠省略
// override關鍵字能夠幫助咱們儘早地發現代碼裏的錯誤,好比:override修飾的父類方法的方法名咱們拼寫錯了;好比要覆蓋的父類方法的參數咱們寫錯了;等等
// 此外,在子類覆蓋父類方法以後,若是咱們在子類中就是要調用父類的被覆蓋的方法呢?那就可使用super關鍵字,顯式地指定要調用父類的方法
class Person {
private var name = "leo"
def getName = name
}
class Student extends Person {
private var score = "A"
def getScore = score
override def getName = "Hi, I'm " + super.getName
}
重寫時須要override關鍵字,若是是實現則能夠省略override關鍵字
子類能夠覆蓋父類的同名的非private成員
// Scala中,子類能夠覆蓋父類的val field,並且子類的val field還能夠覆蓋父類的val field的getter方法;只要在子類中使用override關鍵字便可
class Person {
/*private*/ val name: String = "Person"
}
class Student extends Person {
override val name: String = "leo" // 重寫必定要帶上override關鍵字
}
只有val變量才能被重寫,var變量是不能被重寫的:
class A{
var f = "a"
}
class B extends A{
override var f = "b" // error: overriding variable f in class A of type String;
//variable f cannot override a mutable variable
}
下面也是不行的:
class A{
var f:String = "a"
}
class B extends A{
override def f:String = "b"
override def f_=(x:String) = println(x) // error: overriding variable f in class A of type String;
//method f_= cannot override a mutable variable
}
var變量只能被實現,若是將上面換成抽象的var字段,則是能夠的:
abstract class A{
var f:String
}
class B extends A{
/* override */ def f:String = "b" //因爲是實現,因此能夠省略override
override def f_=(x:String) = println(x) //也能夠不省略override
}
或者:
abstract class A{
var f:String
}
class B extends A{
var f:String = ""
}
val變量只能被val實現,不能被def實現:
abstract class A{
val f:String
}
class B extends A{
def f:String = "" // error: overriding value f in class A of type String;
//method f needs to be a stable, immutable value
}
但能夠這樣:
abstract class A{
val f:String
}
class B extends A{
val f:String = ""
}
abstract class Person {
def id: Int
}
class Student extends Person{
override var id = 9527 //Error: method id_= overrides nothing
}
在scala中定義了一個var變量,會自動生成getter和setter方法。因爲父類中只定義了一個方法def id: Int,而子類中var變量會自動生成getter(id)與setter方法(id_),可是父類並無這個setter方法,因此是沒法重寫的。以下修改便可:
abstractclass Person {
def idInt :
def id_=Int//父類必須有set方法 (value: )
}
class Student extends Person{
overridevar9527//爲var變量自動生成get和set方法 id =
}
或者子類定義成val變量:
abstractclass Person {
def idInt :
}
class Student extends Person{
overrideval9527 id =
}
上面是val或var來實現def,下面是val或var來重寫def:
class Person {
def id: Int = 1
}
class Student extends Person{
override val id = 9527
}
或
class Person {
def id: Int = 1
def id_=(value: Int) =println(value)
}
class Student extends Person{
override var id = 9527
}
可是不能使用def重寫val或var:
class Person {
val sex: String
}
class Student extends Person {
override def sex:String = "" //error: overriding value sex in class Person of type String;
//method sex needs to be a stable, immutable value
}
class Person {
var sex: String = "X"
}
class Student extends Person {
override def sex:String = ""
override def sex_=(x:String) = println(x)
}
也不能使用def實現val:
abstract class Person {
val sex: String
}
class Student extends Person {
def sex:String = "" //error: overriding value sex in class Person of type String;
//method sex needs to be a stable, immutable value
}
但可使用def實現var:
abstract class Person {
var sex: String
}
class Student extends Person {
def sex:String = ""
def sex_=(x:String) = println(x)
}
成員變量與方法之間重寫與實現結論:可使用val或var來重寫或實現def,也可使用def實現var;但不能使用def重寫val或var,也不能使用def實現val
// 若是咱們建立了子類的對象,可是又將其賦予了父類類型的變量。則在後續的程序中,咱們又須要將父類類型的變量轉換爲子類類型的變量,應該如何作?
// 首先,須要使用isInstanceOf判斷對象是不是指定類的對象,若是是的話,則可使用asInstanceOf將對象轉換爲指定類型
// 注意,若是對象是null,則isInstanceOf必定返回false,asInstanceOf必定返回null
// 注意,若是沒有用isInstanceOf先判斷對象是否爲指定類的實例,就直接用asInstanceOf轉換,則可能會拋出異常
class Person
class Student extends Person
val p: Person = new Student
var s: Student = null
if (p.isInstanceOf[Student]) s = p.asInstanceOf[Student]
scala> p.isInstanceOf[Student]
res7: Boolean = true
scala> p.isInstanceOf[Person]
res8: Boolean = true
// isInstanceOf只能判斷出對象是不是給定類或其子類的實例對象,而不能精確判斷出對象就是給定類的實例對象
// 若是要求精確地判斷對象就是指定類的對象,那麼就只能使用getClass和classOf了
// 對象.getClass能夠精確獲取對象所屬的類class,classOf[類]能夠精確獲取類,而後使用==操做符便可判斷
class Person
class Student extends Person
val p: Person = new Student
p.isInstanceOf[Person]
p.getClass == classOf[Person]
p.getClass == classOf[Student]
// 可是在實際開發中,好比spark的源碼中,大量的地方都是使用了模式匹配的方式來進行類型的判斷,這種方式更加地簡潔明瞭,並且代碼得可維護性和可擴展性也很是的高
// 使用模式匹配,功能性上來講,與isInstanceOf同樣,也是判斷主要是該類以及該類的子類的對象便可,也不是精準判斷的
class Person
class Student extends Person
val p: Person = new Student
p match {
case per: Person => println("it's Person's object")
case _ => println("unknown type")
}
// 跟java同樣,scala中一樣可使用protected關鍵字來修飾field和method,這樣子類就能夠繼承這些成員或方法
// 還可使用protected[this],則只能在當前子類對象中訪問父類的使用protected[this]修飾的field和method,沒法經過其餘子類對象訪問父類中的這些字段與方法
class Person {
protected var name: String = "leo"
protected[this] var hobby: String = "game"
}
class Student extends Person {
def sayHello = println("Hello, " + name)
def makeFriends(s: Student) {
println("my hobby is " + hobby + ", your hobby is " + s.hobby) //此處編譯出錯
}
}
protected[this]修飾的字段只能在本對象或其子對象中使用,不能在其餘對象中使用:
class Person {
protected var name: String = "leo"
protected[this] var hobby: String = "game"
def makeFriends(s: Person ){
println("my hobby is " + hobby + ", your hobby is " + s.hobby) //此處編譯仍是出錯
}
}
與private[this]同樣,protected[this]也能夠修飾方法
// Scala中,每一個類能夠有一個主constructor和任意多個輔助constructor,而每一個輔助constructor的第一行都必須是調用其餘輔助constructor或者是主constructor;所以子類的輔助constructor是必定不可能直接調用父類的constructor的
// 只能在子類的主constructor中調用父類的constructor,如下這種語法,就是經過子類的主構造函數來調用父類的構造函數(即在extends後面指定須要調用父類哪一個構造器)
// 注意!若是是父類中接收的參數,好比name和age,子類中接收時,就不要用任何val或var來修飾了(或者帶上修飾了,但將參數名命成不同也可),不然會認爲是子類要覆蓋父類的field
class Person(val name: String, val age: Int)
class Student(name: String, age: Int, var score: Double) extends Person(name, age) /*調用父類的輔助構造器*/{
def this(name: String) {
this(name, 0, 0) //調用主構造器
}
def this(age: Int) {
this("leo", age, 0) //調用主構造器
}
}
調用父類的主構造器:
class Person(val name: String, val age: Int){
def this(){
this("11",11)
}
}
class Student(name: String, age: Int, var score: Double) extends Person/*或 Person()*/ {
def this(name: String) {
this(name, 0, 0) //調用主構造器
}
def this(age: Int) {
this("leo", age, 0) //調用主構造器
}
}
// 在Scala中,匿名子類是很是常見,並且很是強大的。Spark的源碼中也大量使用了這種匿名子類。
// 匿名子類,也就是說,能夠定義一個類的沒有名稱的子類,並直接建立其對象,而後將對象的引用賦予一個變量。以後甚至能夠將該匿名子類的對象傳遞給其餘函數使用。
class Person(protected val name: String) {
def sayHello = "Hello, I'm " + name
}
val p = new Person("leo") {
override def sayHello = "Hi, I'm " + name
}
def greeting(p: Person { def sayHello: String }) {
println(p.sayHello)
}
// 若是在父類中,有某些方法沒法當即實現,而須要依賴不一樣的子來來覆蓋,重寫實現本身不一樣的方法實現。此時能夠將父類中的這些方法不給出具體的實現,只有方法簽名,這種方法就是抽象方法。
// 而一個類中若是有一個抽象方法,那麼類就必須用abstract來聲明爲抽象類,此時抽象類是不能夠實例化的
// 在子類中覆蓋抽象類的抽象方法時,不須要使用override關鍵字(也可帶上),但若是是重寫父類具體方法或成員,則不能省略override
abstract只能修飾類,不能修飾成員與方法,哪怕成員(沒有初始化)與方法(沒有方法體)是抽象的
abstract class Person(val name: String) {
def sayHello: Unit
}
class Student(name: String) extends Person(name) {
def sayHello: Unit = println("Hello, " + name)
}
// 若是在父類中,定義了field,可是沒有給出初始值,則此field爲抽象field
// 抽象field意味着,scala會根據本身的規則,爲var或val類型的field生成對應的getter和setter方法,可是父類中是沒有該field的
// 子類必須覆蓋field,以定義本身的具體field,而且覆蓋抽象field,不須要使用override關鍵字
abstract class Person {
val name: String
}
class Student extends Person {
val name: String = "leo"
}
沒有初始化的成員所在的類要是抽象類:
abstract class A{
var a:String
}
/*class B extends A*/編譯時報錯:須要重寫父類的抽象成員
class B extends A{
/*override*/ var a:String = "a" //也能夠省略override
}
除了經過上面直接覆蓋父類的抽象成員外,還能夠簡接經過實現抽象成員所對應的getter與setter方法便可:
class B extends A{
/*override*/ def a = "a" //因爲是實現,因此能夠省略override
override def a_=(x:String){println(a)}
}
上面是經過實現父類抽象成員所對應的getter與setter方法來重寫抽象成員,因此能夠看出:沒有被初始化的成員所對應的getter與setter方法實質上就是抽象的,因此類要定義是abstract,成員字段自己沒有什麼抽象不抽象的概念
// Scala中的Triat是一種特殊的概念
// 首先咱們能夠將Trait做爲接口來使用,此時的Triat就與Java中的接口很是相似
// 在triat中能夠定義抽象方法,就與抽象類中的抽象方法同樣,只要不給出方法的具體實現便可
// 類可使用extends關鍵字繼承trait,注意,這裏不是implement,而是extends,在scala中沒有implement的概念,不管繼承類仍是trait,統一都是extends
// 類繼承trait後,必須實現其中的抽象方法(若是是trait繼承trait則不須要,這比如Java中的接口繼承接口同樣),實現時不須要使用override關鍵字
// scala不支持對類進行多繼承,可是支持多重繼承trait,使用with關鍵字便可
trait HelloTrait {
def sayHello(name: String)
}
trait MakeFriendsTrait {
def makeFriends(p: Person)
}
class Person(val name: String) extends HelloTrait with MakeFriendsTrait with Cloneable {
def sayHello(name: String) = println("Hello, " + name)
def makeFriends(p: Person) = {sayHello(name);println("Hello, my name is " + name + ", your name is " + p.name)}
}
val p1 = new Person("leo")
val p2 = new Person("lily")
p1.makeFriends(p2)
// Scala中的Triat能夠不是隻定義抽象方法,還能夠定義具體方法,此時trait更像是包含了通用工具方法的東西
// 有一個專有的名詞來形容這種狀況,就是說trait的功能混入了類
// 舉例來講,trait中能夠包含一些不少類都通用的功能方法,好比打印日誌等等,spark中就使用了trait來定義了通用的日誌打印方法
trait Logger {
def log(message: String) = println(message)
}
class Person(val name: String) extends Logger {
def makeFriends(p: Person) {
println("Hi, I'm " + name + ", I'm glad to make friends with you, " + p.name)
log("makeFriends methdo is invoked with parameter Person[name=" + p.name + "]")
}
}
val p1 = new Person("leo")
val p2 = new Person("lily")
p1.makeFriends(p2)
// Scala中的Triat能夠定義具體field,此時繼承trait的類就自動得到了trait中定義的field
trait Person {
val eyeNum: Int = 2
}
class Student(val name: String) extends Person {
def sayHello = println("Hi, I'm " + name + ", I have " + eyeNum + " eyes.")
}
val s = new Student("leo")
s.sayHello
// Scala中的Triat能夠定義抽象field,而trait中的具體方法則能夠基於抽象field來編寫
// 可是繼承trait的類,則必須覆蓋抽象field,提供具體的值
trait SayHello {
val msg: String //抽象字段
def sayHello(name: String) = println(msg + ", " + name) // 具體方法調用抽象字段(實質上是調用val抽象字段所對應的getter抽象方法),至關於Java中的模式方法,另參看這裏
}
class Person(val name: String) extends SayHello {
val msg: String = "hello"
def makeFriends(p: Person) {
sayHello(p.name)
println("I'm " + name + ", I want to make friends with you!")
}
}
val p1 = new Person("leo")
val p2 = new Person("lily")
p1.makeFriends(p2)
// 有時咱們能夠在建立類的對象時,指定該對象混入某個trait,這樣,就只有這個對象混入該trait的方法,而類的其餘對象則沒有
trait Logged {
def log(msg: String) {}
}
trait MyLogger extends Logged {
override def log(msg: String) { println("log: " + msg) }
}
class Person(val name: String) extends Logged {
def sayHello { println("Hi, I'm " + name); log("sayHello is invoked!") }
}
val p1 = new Person("leo")
p1.sayHello // Hi, I'm leo
val p2 = new Person("jack") with MyLogger //實例化時混入
p2.sayHello // Hi, I'm jack
//log: sayHello is invoked!
// Scala中支持讓類繼承多個trait後,依次調用多個trait中的同一個方法,只要讓多個trait的同一個方法中,在方法最後都執行「super.方法」來調用父類方法便可
// 類中調用多個trait中都有的這個方法時,首先會從最右邊的trait的方法開始執行,而後依次往左執行,造成一個調用鏈條
// 這種特性很是強大,其實就至關於設計模式中的責任鏈模式的一種具體實現
trait Handler {
def handle(data: String) {}
}
trait DataValidHandler extends Handler {
override def handle(data: String) {
println("check data: " + data)
super.handle(data)
}
}
trait SignatureValidHandler extends Handler {
override def handle(data: String) {
println("check signature: " + data)
super.handle(data)
}
}
class Person(val name: String) extends SignatureValidHandler with DataValidHandler {
def sayHello = { println("Hello, " + name); handle(name) }
}
val p = new Person("leo")
p.sayHello
Hello, leo
check data: leo
check signature: leo
// 在trait中,是能夠覆蓋父trait的抽象方法的
// 可是覆蓋時,若是使用了「super.方法」形式調用了父類抽象方法,則沒法經過編譯。由於super.方法就會去掉用父trait的抽象方法,此時子trait的該方法仍是會被認爲是抽象的,因此在override的同時還須要加上abstract
// 此時若是要經過編譯,就得給子trait的方法加上abstract override修飾
trait Logger {
def log(msg: String)
}
trait MyLogger extends Logger {
abstract override def log(msg: String) { println("MyLogger.log()");super.log(msg) }
}
class BasicLog extends Logger{
def log(msg: String) { println("BasicLog.log()"); println(msg) }
}
class Person(val name: String) extends BasicLog with MyLogger {
def makeFriends(p: Person) {
println("Hi, I'm " + name + ", I'm glad to make friends with you, " + p.name)
log("makeFriends methdo is invoked with parameter Person[name=" + p.name + "]")
}
}
val p1 = new Person("leo")
val p2 = new Person("lily")
p1.makeFriends(p2)
Hi, I'm leo, I'm glad to make friends with you, lily
MyLogger.log()
BasicLog.log()
makeFriends methdo is invoked with parameter Person[name=lily]
// 在trait中,能夠混合使用具體方法和抽象方法
// 可讓具體方法依賴於抽象方法,而抽象方法則放到繼承trait的類中去實現
// 這種trait其實就是設計模式中的模板設計模式的體現
trait Valid {
def getName: String //抽象方法
def valid: Boolean = { //具體方法中調用抽象方法,至關於Java中的模板方法
getName == "leo"
}
}
class Person(val name: String) extends Valid {
println(valid)
def getName = name
}
val p = new Person("leo") //true
// 在Scala中,trait也是有構造代碼的,也就是trait中的,不包含在任何方法中的代碼
// 而繼承了trait的類的構造機制以下:1、父類的構造函數執行;2、trait的構造代碼執行,多個trait從左到右依次執行;3、構造trait時會先構造父trait,若是多個trait繼承同一個父trait,則父trait只會構造一次;4、全部trait構造完畢以後,子類的構造函數執行
class Person { println("Person's constructor!") }
trait Logger { println("Logger's constructor!") }
trait MyLogger extends Logger { println("MyLogger's constructor!") }
trait TimeLogger extends Logger { println("TimeLogger's constructor!") }
class Student extends Person with MyLogger with TimeLogger {
println("Student's constructor!")
}
val s = new Student
// 在Scala中,trait的構造函數是不能接參數的(包括主構造器與輔助構造器),即trait不能定義輔助構造器,這是trait與class的惟一區別,可是若是需求就是要trait可以對field進行初始化,該怎麼辦呢?只能使用Scala中很是特殊的一種高級特性——提早定義
trait SayHello {
val msg: String
println("1、SayHello")
println(msg.toString) // 拋NullPointerException異常。因爲在調用msg成員字段時,發如今msg是被從新實現(或重寫,這裏爲實現),則會去調用子類中的實現的msg成員,但因爲此時子類構造器還未執行,因此子類msg還沒來得及初始化,因此返回null,最終致使空指針異常
}
class Person extends SayHello{
println("2、Person")
val msg:String = "init"
}
new Person // 拋NullPointerException異常,緣由父trait構造代碼會先於子類構造器執行,在執行msg.toString時子類中的msg尚未來得及初始化。但若是將上面的val都修改成def,則能夠正常運行。由於初始化父類時,因爲子類實現(或重寫,這裏爲實現)了msg方法,因此msg.toString會去調用子類實現的msg方法而返回"init",即便此時子類尚未被初始化:
trait SayHello {
def msg: String
println("1、SayHello")
println(msg.toString)
}
class Person extends SayHello{
println("2、Person")
def msg:String = "init"
}
new Person
即便父類提供了初始化,但仍是拋NullPointerException,緣由是子類重寫了父類該字段msg,在執行父類構造器中的msg.toString時,msg使用的是子類中被重寫過的,但此時子類構造器還未被執行,因此子類的msg此時還爲null:
trait SayHello {
val msg: String = "000"
println("1、SayHello")
println(msg.toString) // NullPointerException
}
class Person extends SayHello{
println("2、Person")
override val msg:String = "init"
}
new Person
而下面的則不會拋異常了,緣由是子類沒有重寫msg字段,因此父類構造器在執行時,msg使用的仍是父中的msg,且已經被初始化過了:
trait SayHello {
val msg: String = "000"
println("1、SayHello")
println(msg.toString) // 不會拋異常,注意:此名要放在上面msg初始化語句的後面,不然仍是會拋空指針
}
class Person extends SayHello{
println("2、Person")
}
new Person
下面根據前面的知識(字段與方法相互實現與重寫),結合上面的經驗,分析分析一下下面的狀況:
如下也能夠,緣由也是經過方法的多態來初始化:
trait SayHello {
var msg: String
println("1、SayHello")
println(msg.toString)//會去調用子類實現方法msg,順利執行
}
class Person extends SayHello{
println("2、Person")
def msg:String = {println("person.msg");"init" }
def msg_=(x:String) = println(x)
}
new Person
trait SayHello {
var msg: String
println("1、SayHello")
println(msg.toString) // 拋 NullPointerException,緣由父類中的msg被子類實現過,但父類調用時,子類還未初始msg字段
}
class Person extends SayHello{
println("2、Person")
var msg:String = "init"
}
new Person
上面除了經過調用子類實現(或重寫)方法解決問題外,下面還能夠經過提早定義方式來初始化:
trait SayHello {
val msg: String
println("3、SayHello")
println(msg.toString)
}
class Person{println("2、Person")}
val p = new {
val msg: String = {println("1、init");"init"} // 實例化時提早初始化
} with Person with SayHello
1、init -> 2、Person -> 3、SayHello
注意上面new … with與class …extends…with的區別,new…with是動態混入,執行構造器是從new後面的類(或塊,這裏爲塊)開始從左到右依次執行;而class…extends…with則是靜態混入,在定義class時就已肯定,其構造器是從extends後面的類開始從左往右依次執行,執行完後最後執行class 後面指定的類的構造器。以下面的new … with形式構造順序:
trait A{
println("a")
}
class B extends A{
println("b")
}
trait C extends A{
println("c")
}
new B with C // a -> b -> c
class…extends…with構造順序:
trait A{
println("a")
}
trait B{
println("b")
}
class C extends A with B{
println("c")
}
new C // a -> b -> c
下面是另外一種初始化方式(class …extends…with靜態定義方式),此種方式比上面初始化方式好理解一點:
trait SayHello {
val msg: String
println("2、SayHello")
println(msg.toString)
}
class Person extends {
val msg: String = {println("1、init");"init"} // 類定義時提早初始化
} with SayHello {
println("3、Person")
}
new Person
// 另一種方式就是使用lazy value
trait SayHello {
lazy val msg: String = {println("SayHello");null} // 此句不會執行
println(msg.toString) // 此句會調用子類重寫過的msg成員,因爲子類msg定義成了lazy,而lazy變量有個特性就是在使用時會執行右邊表達式,因此在這裏調用msg.toString方法時,就會觸發懶加載右邊的計算表達式,因此lazy字段不是由類來初始化的,而是由調用時機來決定,因此子類中的lazy msg會先於子類其餘成員被初始化
println("2")
}
class Person extends SayHello {
println("3")
val m: String = {println("4");"m"}
override lazy val msg: String = {println("1");"init"}
}
new Person
1
init
2
3
4
// 在Scala中,trait也能夠繼承自class,此時這個class就會成爲全部繼承該trait的類的父類
class MyUtil {
def printMessage(msg: String) = println(msg)
}
trait Logger extends MyUtil {
def log(msg: String) = printMessage("log: " + msg)
}
class Person(val name: String) extends Logger {
def sayHello {
log("Hi, I'm " + name)
printMessage("Hi, I'm " + name)
}
}
new Person("leo").sayHello
log: Hi, I'm leo
Hi, I'm leo
// Scala中的函數是一等公民,能夠獨立定義,獨立存在,並且能夠直接將函數做爲值賦值給變量
// Scala的語法規定,將函數賦值給變量時,必須在函數後面加上空格和下劃線
def sayHello(name: String) { println("Hello, " + name) }
val sayHelloFunc = sayHello _
sayHelloFunc("leo")
// Scala中,函數也能夠不須要命名,此時函數被稱爲匿名函數。
// 能夠直接定義函數以後,將函數賦值給某個變量;也能夠將直接定義的匿名函數傳入其餘函數之中
// Scala定義匿名函數的語法規則就是,(參數名: 參數類型) => 函數體
// 這種匿名函數的語法必須深入理解和掌握,在spark的中有大量這樣的語法,若是沒有掌握,是看不懂spark源碼的
val sayHelloFunc = (name: String) => println("Hello, " + name)
sayHelloFunc("leo")
變量帶返回類型:
val sayHelloFunc:String=>Unit = (name: String) => println("Hello, " + name)
// Scala中,因爲函數是一等公民,所以能夠直接將某個函數傳入其餘函數,做爲參數。這個功能是極其強大的,也是Java這種面向對象的編程語言所不具有的。
// 接收其餘函數做爲參數的函數,也被稱做高階函數(higher-order function)
val sayHelloFunc = (name: String) => println("Hello, " + name)
def greeting(func: (String) => Unit, name: String) { func(name) }
greeting(sayHelloFunc, "leo")
Array(1, 2, 3, 4, 5).map((num: Int) => num * num)
// 高階函數的另一個功能是將函數做爲返回值,即返回值就是一個函數,以下面根據不一樣的msg生成不一樣的函數
def getGreetingFunc(msg: String) = (name: String) => println(msg + ", " + name)
var greetingFunc = getGreetingFunc("hello")
greetingFunc("leo")
greetingFunc = getGreetingFunc("hi")
greetingFunc("leo")
// 高階函數能夠自動推斷出參數類型,而不須要寫明類型;並且對於只有一個參數的函數,還能夠省去其小括號;
def greeting(func: (String) => Unit, name: String) { func(name) }
greeting((name: String) => println("Hello, " + name), "leo")
greeting((name) => println("Hello, " + name), "leo")
greeting(name => println("Hello, " + name), "leo")
// 只要某個參數只在函數體裏出現一次,則可使用下劃線 _ 來替換這個參數
def triple(func: (Int) => Int) = { func(3) }
triple(3 * _)
// 諸如3 * _的這種語法,必須掌握!!spark源碼中大量使用了這種語法!
有多少個下劃線,則就表示有多少個不一樣的參數。多個佔位符時,第一個下劃線表示第一個參數,第二個下劃線表示第二個參數,以此類推;因此同一參數多處出現時是沒法使用這種佔位符來表示的。
使用佔位符時,有時沒法推導出類型,如:
scala> val f = _ + _
此時需明確寫出類型:
scala> val f = (_: Int) + (_: Int)
f: (Int, Int) => Int = <function2>
// map: 對傳入的每一個元素都進行映射,返回一個處理後的元素
Array(1, 2, 3, 4, 5).map(2 * _)
// foreach: 對傳入的每一個元素都進行處理,可是沒有返回值
(1 to 9).map("*" * _).foreach(println _)
// filter: 對傳入的每一個元素都進行條件判斷,若是對元素返回true,則保留該元素,不然過濾掉該元素
(1 to 20).filter(_ % 2 == 0)
// reduceLeft: 從左側元素開始,進行reduce操做,即先對元素1和元素2進行處理,而後將結果與元素3處理,再將結果與元素4處理,依次類推,即爲reduce;reduce操做必須掌握!spark編程的重點!!!
// 下面這個操做就至關於1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9
(1 to 9).reduceLeft( _ * _)
// sortWith: 對元素進行兩兩相比,進行排序
Array(3, 2, 5, 4, 10, 1).sortWith(_ < _)
// 閉包最簡潔的解釋:函數在變量不處於其有效做用域時,還可以對變量進行訪問,即爲閉包
def getGreetingFunc(msg: String) = (name: String) => println(msg + ", " + name)
val greetingFuncHello = getGreetingFunc("hello")
val greetingFuncHi = getGreetingFunc("hi")
// 兩次調用getGreetingFunc函數,傳入不一樣的msg,並建立不一樣的函數返回
// 然而,msg只是一個局部變量,卻在getGreetingFunc執行完以後,還能夠繼續存在建立的函數之中;greetingFuncHello("leo"),調用時,值爲"hello"的msg被保留在了函數體內部,能夠反覆的使用
// 這種變量超出了其做用域,還可使用的狀況,即爲閉包
// Scala經過爲每一個函數建立對象來實現閉包,實際上對於getGreetingFunc函數建立的函數,msg是做爲函數對象的變量存在的,所以每一個函數才能夠擁有不一樣的msg
若是Scala調用Java的某個方法傳入的是一個SAM,則能夠經過Scala提供的很方便的一種轉換,將函數對象會傳給Java方法
// 在Java中,因爲不支持直接將函數傳入一個方法做爲參數,一般來講,惟一的辦法就是定義一個實現了某個接口的類的實例對象,該對象只有一個方法,猶如這樣接口只有單個的抽象方法,就叫single abstract method,簡稱爲SAM
// 因爲Scala是能夠調用Java的代碼的,所以當咱們調用Java的某個方法時,可能就不得不建立SAM傳遞給方法,很是麻煩;可是Scala又是支持直接傳遞函數的。此時就可使用Scala提供的,在調用Java方法時,使用Scala提供的SAM轉換功能,即將SAM轉換爲Scala函數
// 要使用SAM轉換,須要使用Scala提供的特性,隱式轉換
import javax.swing._
import java.awt.event._
val button = new JButton("Click")
button.addActionListener(new ActionListener {// ActionListener接口只有一個抽象方法,這樣的接口叫SAM
override def actionPerformed(event: ActionEvent) {
println("Click Me!!!")
}
})
implicit def getActionListener(actionProcessFunc: (ActionEvent) => Unit) = new ActionListener {
override def actionPerformed(event: ActionEvent) {
actionProcessFunc(event)
}
}
button.addActionListener((event: ActionEvent) => println("Click Me!!!"))
// Curring函數,指的是,將原來接收兩個參數的一個函數,轉換爲兩個函數,第一個函數接收原先的第一個參數,而後返回接收原先第二個參數的第二個函數。
// 在函數調用的過程當中,就變爲了兩個函數連續調用的形式
// 在Spark的源碼中,也有體現,因此對()()這種形式的Curring函數,必須掌握!
def sum(a: Int, b: Int) = a + b
sum(1, 1)
def sum2(a: Int) = (b: Int) => a + b
sum2(1)(1)
def sum3(a: Int)(b: Int) = a + b
sum2(1)(1)
// Scala中,不須要使用return來返回函數的值,函數最後一行語句的值,就是函數的返回值。在Scala中,return用於在匿名函數中返回值給包含匿名函數的帶名函數(即外層函數),並做爲帶名函數的返回值。
// 使用return的匿名函數,是必須給出返回類型的,不然沒法經過編譯
def greeting(name: String) = {
def sayHello(name: String):String = {
return "Hello, " + name
}
sayHello(name)
}
greeting("leo")
// Scala中的集合體系主要包括:Iterable、Seq、Set、Map。其中Iterable是全部集合trait的根trait。這個結構與Java的集合體系很是類似(最上層爲public interface Collection<E> extends Iterable<E>)。
// Scala中的集合是分紅可變和不可變兩類集合的,其中可變集合就是說,集合的元素能夠動態修改,而不可變集合的元素在初始化以後,就沒法修改了。分別對應scala.collection.mutable和scala.collection.immutable兩個包。
// Seq下包含了Range、ArrayBuffer、List等子trait。其中Range就表明了一個序列,一般可使用「1 to 10」這種語法來產生一個Range。 ArrayBuffer就相似於Java中的ArrayList。
// List表明一個不可變的列表
// List的建立,val list = List(1, 2, 3, 4)
// List有head和tail,head表明List的第一個元素,tail表明第一個元素以後的全部元素,list.head,list.tail
// List有特殊的::操做符,能夠用於將head和tail合併成一個List,0 :: list
// ::這種操做符要清楚,在spark源碼中都是有體現的,必定要可以看懂!
// 若是一個List只有一個元素,那麼它的head就是這個元素,它的tail是Nil
// 案例:用遞歸函數來給List中每一個元素都加上指定前綴,並打印加上前綴的元素
def decorator(l: List[Int], prefix: String) {
if (l != Nil) {
println(prefix + l.head)
decorator(l.tail, prefix)
}
}
// LinkedList表明一個可變的列表,使用elem能夠引用其頭部,使用next能夠引用其尾部
// val l = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5); l.elem; l.next
// 案例:使用while循環將LinkedList中的每一個元素都乘以2
val list = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5)
var currentList = list
while (currentList != Nil) {
currentList.elem = currentList.elem * 2
currentList = currentList.next
}
// 案例:使用while循環將LinkedList中,從第一個元素開始,每隔一個元素,乘以2
val list = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
var currentList = list
var first = true
while (currentList != Nil && currentList.next != Nil) {
if (first) { currentList.elem = currentList.elem * 2; first = false }
currentList = currentList.next.next
if (currentList != Nil) currentList.elem = currentList.elem * 2
}
// Set表明一個沒有重複元素的集合,Set爲trait,分爲可變與不可變兩種trait
// 將重複元素加入Set是沒有用的,好比val s = Set(1, 2, 3); s + 1; s + 4
// 並且Set是不保證插入順序的,也就是說,Set中的元素是亂序的,val s = new scala.collection.mutable.HashSet[Int](); s += 1; s += 2; s += 5
// LinkedHashSet會用一個鏈表維護插入順序,val s = new scala.collection.mutable.LinkedHashSet[Int](); i += 1; s += 2; s += 5
// SrotedSet會自動根據key來進行排序,val s = scala.collection.mutable.SortedSet("orange", "apple", "banana")
// 集合的函數式編程很是很是很是之重要!!!
// 必須徹底掌握和理解Scala的高階函數是什麼意思,Scala的集合類的map、flatMap、reduce、reduceLeft、foreach等這些函數,就是高階函數,由於能夠接收其餘函數做爲參數
// 高階函數的使用,也是Scala與Java最大的一點不一樣!!!由於Java裏面是沒有函數式編程的,也確定沒有高階函數,也確定沒法直接將函數傳入一個方法,或者讓一個方法返回一個函數
// 對Scala高階函數的理解、掌握和使用,能夠大大加強你的技術,並且也是Scala最有誘惑力、最有優點的一個功能!!!
// 此外,在Spark源碼中,有大量的函數式編程,以及基於集合的高階函數的使用!!!因此必須掌握,才能看懂spark源碼
// map案例實戰:爲List中每一個元素都添加一個前綴
List("Leo", "Jen", "Peter", "Jack").map("name is " + _)
// faltMap案例實戰:將List中的多行句子拆分紅單詞
List("Hello World", "You Me").flatMap(_.split(" "))
// foreach案例實戰:打印List中的每一個單詞
List("I", "have", "a", "beautiful", "house").foreach(println(_))
// zip案例實戰:對學生姓名和學生成績進行關聯
List("Leo", "Jen", "Peter", "Jack").zip(List(100, 90, 75, 83))
// 使用scala的io包將文本文件內的數據讀取出來
val lines01 = scala.io.Source.fromFile("C://Users//Administrator//Desktop//test01.txt").mkString
val lines02 = scala.io.Source.fromFile("C://Users//Administrator//Desktop//test02.txt").mkString
// 使用List的伴生對象,將多個文件內的內容建立爲一個List
val lines = List(lines01, lines02)
// 下面這一行纔是咱們的案例的核心和重點,由於有多個高階函數的鏈式調用,以及大量下劃線的使用,若是沒有透徹掌握以前的課講解的Scala函數式編程,那麼下面這一行代碼,徹底可能會看不懂!!!
// 可是下面這行代碼其實就是Scala編程的精髓所在,就是函數式編程,也是Scala相較於Java等編程語言最大的功能優點所在
// 並且,spark的源碼中大量使用了這種複雜的鏈式調用的函數式編程
// 並且,spark自己提供的開發人員使用的編程api的風格,徹底沿用了Scala的函數式編程,好比Spark自身的api中就提供了map、flatMap、reduce、foreach,以及更高級的reduceByKey、groupByKey等高階函數
// 若是要使用Scala進行spark工程的開發,那麼就必須掌握這種複雜的高階函數的鏈式調用!!!
lines.flatMap(_.split(" ")).map((_, 1)).map(_._2).reduceLeft(_ + _)
// Scala是沒有Java中的switch case語法的,相對應的,Scala提供了更增強大的match case語法,即模式匹配,類替代switch case,match case也被稱爲模式匹配
// Scala的match case與Java的switch case最大的不一樣點在於,Java的switch case僅能匹配變量的值,比1、2、3等;而Scala的match case能夠匹配各類狀況,好比變量的類型、集合的元素、有值或無值
// match case的語法以下:變量 match { case 值 => 代碼 }。若是值爲下劃線,則表明了不知足以上全部狀況下的默認狀況如何處理。此外,match case中,只要一個case分支知足並處理了,就不會繼續判斷下一個case分支了。(與Java不一樣,java的switch case須要用break阻止)
// match case語法最基本的應用,就是對變量的值進行模式匹配
// 案例:成績評價
def judgeGrade(grade: String) {
grade match {
case "A" => println("Excellent")
case "B" => println("Good")
case "C" => println("Just so so")
case _ => println("you need work harder")
}
}
// Scala的模式匹配語法,有一個特色在於,能夠在case後的條件判斷中,不只僅只是提供一個值,而是能夠在值後面再加一個if守衛,進行雙重過濾
// 案例:成績評價(升級版)
def judgeGrade(name: String, grade: String) {
grade match {
case "A" => println(name + ", you are excellent")
case "B" => println(name + ", you are good")
case "C" => println(name + ", you are just so so")
case _ if name == "leo" => println(name + ", you are a good boy, come on")
case _ => println("you need to work harder")
}
}
// Scala的模式匹配語法,有一個特色在於,能夠將模式匹配的默認狀況,將下劃線替換爲一個變量名,此時模式匹配語法就會將要匹配的值賦值給這個變量,從而能夠在後面的處理語句中使用要匹配的值
// 爲何有這種語法??思考一下。由於只要使用用case匹配到的值,是否是咱們就知道這個只啦!!在這個case的處理語句中,是否是就直接可使用寫程序時就已知的值!
// 可是對於下劃線_這種狀況,全部不知足前面的case的值,都會進入_這種默認狀況進行處理,此時若是咱們在處理語句中須要拿到具體的值進行處理呢?那就須要使用這種在模式匹配中進行變量賦值的語法!!
// 案例:成績評價(升級版)
def judgeGrade(name: String, grade: String) {
grade match {
case "A" => println(name + ", you are excellent")
case "B" => println(name + ", you are good")
case "C" => println(name + ", you are just so so")
case grade_ if name == "leo" => println(name + ", you are a good boy, come on, your grade is " + grade+ " : " + grade_)
case _ => println("you need to work harder, your grade is " + grade)
}
}
// Scala的模式匹配一個強大之處就在於,能夠直接匹配類型,而不是值!!!這點是java的switch case絕對作不到的。
// 理論知識:對類型如何進行匹配?其餘語法與匹配值實際上是同樣的,可是匹配類型的話,就是要用「case 變量: 類型 => 代碼」這種語法,而不是匹配值的「case 值 => 代碼」這種語法。
// 案例:異常處理
import java.io._
def processException(e: Exception) {
e match {
case e1: IllegalArgumentException => println("you have illegal arguments! exception is: " + e1)
case e2: FileNotFoundException => println("cannot find the file you need read or write!, exception is: " + e2)
case e3: IOException => println("you got an error while you were doing IO operation! exception is: " + e3)
case _: Exception => println("cannot know which exception you have!" )
}
}
processException(new IOException ("File not found"))
// 對Array進行模式匹配,分別能夠匹配帶有指定元素的數組、帶有指定個數元素的數組、以某元素打頭的數組
// 對List進行模式匹配,與Array相似,可是須要使用List特有的::操做符
// 案例:對朋友打招呼
def greeting(arr: Array[String]) {
arr match {
case Array("Leo") => println("Hi, Leo!")
case Array(girl1, girl2, girl3) => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
case Array("Leo", _*) => println("Hi, Leo, please introduce your friends to me.")
case _ => println("hey, who are you?")
}
}
greeting(Array("Leo","Jack"))
def greeting(list: List[String]) {
list match {
case "Leo" :: Nil => println("Hi, Leo!")
case girl1 :: girl2 :: girl3 :: Nil => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
case "Leo" :: tail => println("Hi, Leo, please introduce your friends to me.")
case _ => println("hey, who are you?")
}
}
greeting(List("Leo","Jack"))
// Scala中提供了一種特殊的類,用case class進行聲明,中文也能夠稱做樣例類。case class其實有點相似於Java中的JavaBean的概念。即只定義field,而且由Scala編譯時自動提供getter和setter方法,可是沒有method。
// case class的主構造函數接收的參數一般不須要使用var或val修飾,Scala自動就會使用val修飾(可是若是你本身使用var修飾,那麼仍是會按照var來)
// Scala自動爲case class定義了伴生對象,也就是object,而且定義了apply()方法,該方法接收主構造函數中相同的參數,並返回case class對象
// 案例:學校門禁
class Person
case class Teacher(name: String, subject: String) extends Person
case class Student(name: String, classroom: String) extends Person
def judgeIdentify(p: Person) {
p match {
case Teacher(name, subject) => println("Teacher, name is " + name + ", subject is " + subject)
case Student(name, classroom) => println("Student, name is " + name + ", classroom is " + classroom)
case _ => println("Illegal access, please go out of the school!")
}
}
judgeIdentify(Student("Leo","1"))
// Scala有一種特殊的類型,叫作Option。Option有兩種值,一種是Some,表示有值,一種是None,表示沒有值。
// Option一般會用於模式匹配中,用於判斷某個變量是有值仍是沒有值,這比null來的更加簡潔明瞭
// Option的用法必須掌握,由於Spark源碼中大量地使用了Option,好比Some(a)、None這種語法,所以必須看得懂Option模式匹配,纔可以讀懂spark源碼。
// 案例:成績查詢
val grades = Map("Leo" -> "A", "Jack" -> "B", "Jen" -> "C")
def getGrade(name: String) {
val grade = grades.get(name)
grade match {
case Some(grade1) => println("your grade is " + grade1)
case None => println("Sorry, your grade information is not in the system")
}
}
getGrade("Lily")
getGrade("Leo")
Scala集合類的某些標準操做會產生Option可選值,如Map的get方法,查到值時返回Some(value)對象,沒查到時返回None對象(而Java中返回的爲Null,這會容易致使程序運行錯誤)
類型參數是什麼?類型參數其實就相似於Java中的泛型。先說說Java中的泛型是什麼,好比咱們有List a = new ArrayList(),接着a.add(1),沒問題,a.add("2"),而後咱們a.get(1) == 2,對不對?確定不對了,a.get(1)獲取的實際上是個String——"2",String——"2"怎麼可能與一個Integer類型的2相等呢?
因此Java中提出了泛型的概念,其實也就是類型參數的概念,此時能夠用泛型建立List,List a = new ArrayList[Integer](),那麼,此時a.add(1)沒問題,而a.add("2")呢?就不行了,由於泛型會限制,只能往集合中添加Integer類型,這樣就避免了上述的問題。
那麼Scala的類型參數是什麼?其實意思與Java的泛型是同樣的,也是定義一種類型參數,好比在集合,在類,在函數中,定義類型參數,而後就能夠保證使用到該類型參數的地方,就確定,也只能是這種類型。從而實現程序更好的健壯性。
此外,類型參數是Spark源碼中很是常見的,所以一樣必須掌握,才能看懂spark源碼。
// 泛型類(類聲明時類名後面中括號中的即爲類型參數),顧名思義,其實就是在類的聲明中,定義一些泛型類型,而後在類內部,好比field或者method,就可使用這些泛型類型。
// 使用泛型類,一般是須要對類中的某些成員,好比某些field和method中的參數或變量,進行統一的類型限制,這樣能夠保證程序更好的健壯性和穩定性。
// 若是不使用泛型進行統一的類型限制,那麼在後期程序運行過程當中,不免會出現問題,好比傳入了不但願的類型,致使程序出問題。
// 在使用類的時候,好比建立類的對象,將類型參數替換爲實際的類型,便可。
案例:新生報到,每一個學生來自不一樣的地方,id多是Int,多是String
class Student[T](val localId: T) { // 在類參數中使用類型參數
def getSchoolId(hukouId: T) = "S-" + hukouId + "-" + localId // 在方法參數中使用類型參數
}
val leo = new Student[Int](111)
// Scala自動推斷泛型類型特性:直接給使用了泛型類型的field賦值時,Scala會自動進行類型推斷。
scala> val leo = new Student(111)
leo: Student[Int] = Student@f001896
scala> val leo = new Student("string")
leo: Student[String] = Student@488eb7f2
// 泛型函數(方法聲明時方法名後面中括號中的即爲類型參數),與泛型類相似,能夠給某個函數在聲明時指定泛型類型,而後在函數體內,多個變量或者返回值之間,就可使用泛型類型進行聲明,從而對某個特殊的變量,或者多個變量,進行強制性的類型限制。
案例:卡片售賣機,能夠指定卡片的內容,內容能夠是String類型或Int類型
def getCard[T](content: T) = {
if(content.isInstanceOf[Int]) "card: 001, " + content
else if(content.isInstanceOf[String]) "card: this is your card, " + content
else "card: " + content
}
getCard[String]("hello world")
getCard[Double](0.01)
// 與泛型類同樣,你能夠經過給使用了泛型類型的變量傳遞值來讓Scala自動推斷泛型的實際類型,也能夠在調用函數時,手動指定泛型類型,上面就是在調用時手動在中括號中指定的,下面靠傳入值自動推斷:
scala> getCard ("hello world")
res2: String = card: this is your card, hello world
scala> getCard (0.01)
res3: String = card: 0.01
// 在指定泛型類型的時候,有時,咱們須要對泛型類型的範圍進行界定,而不是能夠是任意的類型。好比,咱們可能要求某個泛型類型,它就必須是某個類的子類,這樣在程序中就能夠放心地調用泛型類型繼承的父類的方法,程序才能正常的使用和運行。此時就可使用上下邊界Bounds的特性。以下面沒有使用上邊界時,是不能調用類型參數相關方法的:
class Person(val name: String) {
def makeFriends(p: Person) {}
}
class Party[T](p1: T, p2: T) {
def play = p1.makeFriends(p2) // 編譯會出錯
}
// Scala的上下邊界特性容許泛型類型必須是某個類的子類,或者必須是某個類的父類
案例:在派對上交朋友
class Person(val name: String) {
def sayHello = println("Hello, I'm " + name)
def makeFriends(p: Person) {
sayHello
p.sayHello
}
}
class Student(name: String) extends Person(name)
class Party[T <: Person](p1: T, p2: T) { // <:要求T必須是Person或其子類,因爲p1 類型爲Person或其子類Student,因此能夠調用父類中的方法
def play = p1.makeFriends(p2)
}
val leo = new Student("Leo")
val lily = new Student("Lily")
new Party(leo,lily).play
// 除了指定泛型類型的上邊界,還能夠指定下邊界,即指定泛型類型必須是某個類的父類
案例:領身份證(只能是本身或父親代領)
class Father(val name: String)
class Child(name: String) extends Father(name)
def getIDCard[R >: Child](person: R) { // R必須是Child的父類
if (person.getClass == classOf[Child]) println("please tell us your parents' names.")
else if (person.getClass == classOf[Father]) println("sign your name for your child's id card.")
else println("sorry, you are not allowed to get id card.")
}
val f = new Father("Father")
val c = new Child("Child")
scala> getIDCard[Father](f)
sign your name for your child's id card.
scala> getIDCard[Child](c)
please tell us your parents' names.
scala> getIDCard(f) // 類型自動推斷
sign your name for your child's id card.
scala> getIDCard(c) // 類型自動推斷
please tell us your parents' names.
val s = new Student("Student")
scala> getIDCard(s)
sorry, you are not allowed to get id card.
注:下邊界與上邊界是不一樣的,上邊界是爲了能夠調用類型參數相應方法,而下邊界是爲了限制泛型類或泛型函數只適用於哪些類,而不是爲了調用類型參數相應方法。
// 上下邊界Bounds,雖然可讓一種泛型類型,支持有父子關係的多種類型。可是,在某個類與上下邊界Bounds指定的父子類型範圍內的類都沒有任何關係,則默認是確定不能接受的。
// 然而,View Bounds做爲一種上下邊界Bounds的增強版,支持能夠對類型進行隱式轉換,將指定的類型進行隱式轉換後,再判斷是否在邊界指定的類型範圍內
案例:跟小狗交朋友
class Person(val name: String) {
def sayHello = println("Hello, I'm " + name)
def makeFriends(p: Person) {
sayHello
p.sayHello
}
}
class Student(name: String) extends Person(name)
class Dog(val name: String) { def sayHello = println("Wang, Wang, I'm " + name) }
implicit def dog2person(o: Object): Person =
if(o.isInstanceOf[Dog]) {println("-D-");val _o = o.asInstanceOf[Dog]; new Person(_o.name){
override def sayHello = _o.sayHello
} }// 若是是狗,隱式的轉換爲人
//注:即便是Person或Student,也要將Object強轉成Person後返回,而不能直接將o返回,不然可能引發循環調用隱式轉換
else if(o.isInstanceOf[Person]) {println("-P-");val _o = o.asInstanceOf[Person];_o} // 若是是人,不用轉換,只是強轉型後返回
else {println("-O-");error(o.toString)} //其餘狀況返回Nothing
class Party[T <% Person](p1: T){ // <%表示T能夠是Person或其子類,或者是能夠通過隱式轉換後成爲Person或其子類的類
def play() = {println(p1.name);println("--------------------")}
}
class Party2[T <% Person](p1: T,p2: T){
def play() = {p1.makeFriends(p2);println("--------------------")}
}
val leo = new Person("Leo")
val lily = new Student("Lily")
new Party(leo).play()
new Party(lily).play()
val dog = new Dog("Dog")
new Party(dog).play()//發生隱式轉換
new Party2(lily,leo).play()
new Party2(dog,leo).play()//發生隱式轉換
new Party2(lily,dog).play()//發生隱式轉換
// Context Bounds是一種特殊的Bounds,它會根據泛型類型的聲明,好比「T: 類型」要求必須存在一個類型爲「類型[T]」的隱式值(運行時Scala會幫咱們自動注入這個已存在的隱式值)。其實我的認爲,Context Bounds之因此叫Context,是由於它基於的是一種全局的上下文,須要使用到上下文中的隱式值以及注入。
案例:使用Scala內置的比較器比較大小
// Ordering[T]相似Java中的Comparator<T>比較器
class Calculator[T: Ordering] (val number1: T, val number2: T) {
//運行時,Scala會在上下文中去找類型爲Ordering[T]的隱式值並注進來,因此調用該方法時不須要傳遞該參數值了
def max(implicit order: Ordering[T]) = if(order.compare(number1, number2) > 0) number1 else number2
}
new Calculator(3,4).max
// 在Scala中,若是要實例化一個泛型數組,就必須使用Manifest Context Bounds。也就是說,若是數組元素類型爲T的話,須要爲類或者函數定義[T: Manifest]泛型類型,這樣才能實例化Array[T]這種泛型數組。
案例:打包飯菜(一種食品打成一包)
class Meat(val name: String)
class Vegetable(val name: String)
def packageFood[T: Manifest] (food: T*) = {
// 建立泛型數組Array[T]:即數組中的元素類型要在運行時才能肯定,編譯時沒法肯定,元素類型爲動態
val foodPackage = new Array[T](food.length)
for(i <- 0 until food.length) foodPackage(i) = food(i)
foodPackage
}
val gongbaojiding = new Meat("gongbaojiding")
val yuxiangrousi = new Meat("yuxiangrousi")
val shousiyangpai = new Meat("shousiyangpai")
val meatPackageFood = packageFood(gongbaojiding,yuxiangrousi,shousiyangpai)
meatPackageFood: Array[Meat] = Array(Meat@20b829d5, Meat@7c5f29c6, Meat@4baf997)
val qingcai = new Vegetable("qingcai")
val baicai = new Vegetable("baicai")
val huanggua = new Vegetable("huanggua")
val vegPackageFood = packageFood(qingcai,baicai,huanggua)
vegPackageFood: Array[Vegetable] = Array(Vegetable@583030bd, Vegetable@2ac3d530, Vegetable@2431050d)
// Scala的協變和逆變是很是有特點的!徹底解決了Java中的泛型的一大缺憾!
// 舉例來講,Java中,若是有Professional是Master的子類,那麼Card[Professionnal]是否是Card[Master]的子類?答案是:不是。所以對於開發程序形成了不少的麻煩。
// 而Scala中,只要靈活使用協變和逆變,就能夠解決Java泛型的問題。
案例:進入會場
class Master // 大師
class Professional extends Master // 專家,按理來講,大師是一種專家,應該是Master爲Professional子類纔對
// 大師以及專家的名片均可以進入會場
class Card[+T] (val name: String) // 協變:當類型B是類型A的子類型,則能夠認爲T[B]是T[A]的子類
def enterMeet(card: Card[Master]) {
println("welcome to have this meeting!")
}
// 若是去掉+加號,則Card[Professional]不能傳入到enterMeet方法
//專家級別及以上大師級別的名片就能夠進入會場
class Card[-T] (val name: String) // 逆變:當類型B是類型A的子類型,則反過來能夠認爲T[A]是T[B]的子類型
// 要想父類也能夠傳進來,則要讓Card進行逆變,這樣Card[Master]就反過來成爲Card[Professional]的子類,因此就能傳進來了
def enterMeet(card: Card[Professional]) {
println("welcome to have this meeting!")
}
// 在Scala裏,有一種特殊的類型參數,就是Existential Type,存在性類型。這種類型務必掌握是什麼意思,由於在spark源碼實在是太常見了!
Array[T] forSome { type T }
Array[_]
scala> def foo[T](x : Array[T]) = println(x.length)
foo: [T](x: Array[T])Unit
scala> foo(Array[String]("foo", "bar", "baz"))
3
scala> def foo(x : Array[T] forSome { type T}) = println(x.length)
foo: (x: Array[_])Unit
scala> foo(Array[String]("foo", "bar", "baz"))
3
scala> def foo(x : Array[_]) = println(x.length)
foo: (x: Array[_])Unit
scala> foo(Array[String]("foo", "bar", "baz"))
3
scala> def foo(x : Array[T] forSome { type T <: CharSequence}) = x.foreach(y => println(y.length))
foo: (x: Array[_ <: CharSequence])Unit
scala> foo(Array[String]("foo", "bar", "baz"))
3
3
3
Scala提供的隱式轉換和隱式參數功能,是很是有特點的功能。是Java等編程語言所沒有的功能。它能夠容許你手動指定,將某種類型的對象轉換成其餘類型的對象。經過這些功能,能夠實現很是強大,並且特殊的功能。
Scala的隱式轉換,其實最核心的就是定義隱式轉換函數,即implicit conversion function。定義的隱式轉換函數,只要在編寫的程序內引入,就會被Scala自動使用。Scala會根據隱式轉換函數的簽名,在程序中使用到隱式轉換函數接收的參數類型定義的對象時,會自動將其傳入隱式轉換函數,轉換爲另一種類型的對象並返回。這就是「隱式轉換」。
隱式轉換函數叫什麼名字是無所謂的,由於一般不會由用戶手動調用,而是由Scala進行調用。可是若是要使用隱式轉換,則須要對隱式轉換函數進行導入。所以一般建議將隱式轉換函數的名稱命名爲「one2one」的形式。
Spark源碼中有大量的隱式轉換和隱式參數,所以必須精通這種語法。
// 要實現隱式轉換,只要程序可見的範圍內定義隱式轉換函數便可。Scala會自動使用隱式轉換函數。隱式轉換函數與普通函數惟一的語法區別就是,要以implicit開頭,並且最好要定義函數返回類型。
// 案例:特殊售票窗口(只接受特殊人羣,好比學生、老人等)
class SpecialPerson(val name: String)
class Student(val name: String)
class Older(val name: String)
implicit def object2SpecialPerson (obj: Object): SpecialPerson = {
if (obj.getClass == classOf[Student]) { val stu = obj.asInstanceOf[Student]; new SpecialPerson(stu.name) }
else if (obj.getClass == classOf[Older]) { val older = obj.asInstanceOf[Older]; new SpecialPerson(older.name) }
else error(obj.toString)
}
var ticketNumber = 0
def buySpecialTicket(p: SpecialPerson) = {//只針對特殊人羣賣票,因此若是是學生與老人,則要先轉換爲特殊人羣
ticketNumber += 1
"T-" + ticketNumber
}
class Teacher(val name:String)
scala> val tom = new Teacher("tom")
scala> buySpecialTicket(tom)
java.lang.RuntimeException: Teacher@277f7dd3
at scala.sys.package$.error(package.scala:27)
at scala.Predef$.error(Predef.scala:144)
at .object2SpecialPerson(<console>:17)
... 32 elided
scala> val s = new Student("student")
scala> val o = new Older("older")
scala> buySpecialTicket(s)
res1: String = T-1
scala> buySpecialTicket(o)
res2: String = T-2
// 隱式轉換很是強大的一個功能,就是能夠在不知不覺中增強現有類型的功能(有點像Java中的裝飾模式)。也就是說,能夠爲某個類定義一個增強版的類,並定義互相之間的隱式轉換,從而讓源類在使用增強版的方法時,由Scala自動進行隱式轉換爲增強類,而後再調用該方法(如內置的Int 類的增強版 RichInt)。
// 案例:超人變身
class Man(val name: String)
class Superman(val name: String) {
def emitLaser = println("emit a laster!") // 超人才有該方法
}
implicit def man2superman(man: Man): Superman = new Superman(man.name)
val leo = new Man("leo") // 普通人
leo.emitLaser // 調用不存在的方法時,會自動的先轉換爲超人
// Scala默認有兩種找隱式轉換的方式:首先是從源類型或者目標類型,這兩類型的伴生對象中找隱式轉換函數;而後在當前程序做用域內找隱式轉換函數。
// 若是隱式轉換函數不在上述兩種狀況下的話,那麼就必須手動使用import語法引入某個包下的隱式轉換函數,好比import test._。一般建議,僅僅在須要進行隱式轉換的地方,好比某個函數或者方法內,用import導入隱式轉換函數,這樣能夠縮小隱式轉換函數的做用域,避免不須要的隱式轉換。
// 1、調用某個函數,可是給函數傳入的參數的類型,與函數定義的接收參數類型不匹配(案例:特殊售票窗口)
// 2、使用某個類型的對象,調用某個方法,而這個方法並不存在於該類型時(案例:超人變身)
// 3、使用某個類型的對象,調用某個方法,雖然該類型有這個方法,可是傳給方法的參數類型與方法定義的接收參數的類型不匹配(案例:特殊售票窗口增強版)
//4、將一種類型賦值給另外一種類型時,若是類型不兼容時
// 案例:特殊售票窗口增強版
class TicketHouse {
var ticketNumber = 0
def buySpecialTicket(p: SpecialPerson) = {
ticketNumber += 1
"T-" + ticketNumber
}
}
new TicketHouse().buySpecialTicket(new Student("student"))
// 所謂的隱式參數,指的是在函數或者方法中,定義一個用implicit修飾的參數,此時Scala會嘗試找到一個指定類型的,用implicit修飾的對象,即隱式值,並自動注入到該隱式參數。
// Scala會在兩個範圍內查找:一種是當前做用域內定義val或var隱式變量;一種是從隱式參數類型的伴生對象內找隱式值
// 案例:考試簽到
class SignPen {
def write(content: String) = println(content)
}
implicit val signPen = new SignPen
def signForExam(name: String) (implicit signPen: SignPen) {
signPen.write(name + " come to exam in time.")
}
Scala的Actor相似於Java中的多線程編程。可是不一樣的是,Scala的Actor提供的模型與多線程有所不一樣。Scala的Actor儘量地避免鎖和共享狀態,從而避免多線程併發時出現資源爭用的狀況,進而提高多線程編程的性能。此外,Scala Actor的這種模型還能夠避免死鎖等一系列傳統多線程編程的問題。
Spark中使用的分佈式多線程框架,是Akka。Akka也實現了相似Scala Actor的模型,其核心概念一樣也是Actor。所以只要掌握了Scala Actor,那麼在Spark源碼研究時,至少便可看明白Akka Actor相關的代碼。可是,換一句話說,因爲Spark內部有大量的Akka Actor的使用,所以對於Scala Actor也至少必須掌握,這樣才能學習Spark源碼。
// Scala提供了Actor trait來讓咱們更方便地進行actor多線程編程,就Actor trait就相似於Java中的Thread和Runnable同樣,是基礎的多線程基類和接口。咱們只要重寫Actor trait的act方法,便可實現本身的線程執行體,與Java中重寫run方法相似。
// 此外,使用start()方法啓動actor;使用!符號,向actor發送消息;actor內部使用receive和模式匹配接收消息
// 案例:Actor Hello World
import scala.actors.Actor
class HelloActor extends Actor {
def act() {
while (true) {
receive {
case name: String => println("Hello, " + name)
}
}
}
}
val helloActor = new HelloActor
helloActor.start()
helloActor ! "leo"
// Scala的Actor模型與Java的多線程模型之間,很大的一個區別就是,Scala Actor自然支持線程之間的精準通訊;即一個actor能夠給其餘actor直接發送消息。這個功能是很是強大和方便的。
// 要給一個actor發送消息,須要使用「actor ! 消息」的語法。在scala中,一般建議使用樣例類,即case class來做爲消息進行發送。而後在actor接收消息以後,可使用scala強大的模式匹配功能來進行不一樣消息的處理。
// 案例:用戶註冊登陸後臺接口
import scala.actors.Actor
case class Login(username: String, password: String)
case class Register(username: String, password: String)
class UserManageActor extends Actor {
def act() {
while (true) {
receive {
case Login(username, password) => println("login, username is " + username + ", password is " + password)
case Register(username, password) => println("register, username is " + username + ", password is " + password)
}
}
}
}
val userManageActor = new UserManageActor
userManageActor.start()
userManageActor ! Register("leo", "1234"); userManageActor ! Login("leo", "1234")
// 若是兩個Actor之間要互相收發消息,那麼scala的建議是,一個actor向另一個actor發送消息時,同時帶上本身的引用;其餘actor收到本身的消息時,直接經過發送消息的actor的引用,便可以給它回覆消息。
// 案例:打電話
import scala.actors.Actor
case class Message(content: String, sender: Actor)
class LeoTelephoneActor extends Actor {
def act() {
while (true) {
receive {
case Message(content, sender) => { println("leo telephone: " + content); sender ! "I'm leo, please call me after 10 minutes." }
}
}
}
}
class JackTelephoneActor(val leoTelephoneActor: Actor) extends Actor {
def act() {
leoTelephoneActor ! Message("Hello, Leo, I'm Jack.", this)
receive {
case response: String => println("jack telephone: " + response)
}
}
}
val leoTel = new LeoTelephoneActor
val jackTel = new JackTelephoneActor(leoTel)
leoTel.start
jackTel.start
// 默認狀況下,消息都是異步的;可是若是但願發送的消息是同步的,即對方接受後,必定要給本身返回結果,那麼可使用!?的方式發送消息。即val reply = actor !? message。
// 若是要異步發送一個消息,可是在後續要得到消息的返回值,那麼可使用Future。即!!語法。val future = actor !! message。val reply = future()。