Scala
Scala介紹
Scala是一門多範式的編程語言, 它與Java極其相似, 都是運行在JVM上的編程語言. Scala設計的初衷是將面向對象編程和函數式編程的各種特性集成起來。
Scala是一門編譯型語言, 同時也是一門弱類型語言, 它的語法簡單, 但是靈活, 極大的靈活性使得初學者對其難以駕馭. 大數據中Spark底層是由Scala語言實現, 因此想要學習Spark, 要先入門Scala.
Scala程序運行時, 會先編譯成Java字節碼文件, 然後該文件再在JVM上運行.
Scala六個特徵
- 可以和Java無縫混編
- 支持類型推測(自動推測類型)
- 併發和分佈式(Actor, 類似Java中的Thread)
- 結合Java中interface實現和abstract繼承的新特性trait
- 模式匹配match(類似Java中的switch, 但支持的類型更加豐富)
- 高階函數(面向函數編程)
- 函數參數是函數
- 函數返回是函數
Scala安裝
1. windows下安裝, 環境配置
官網下載scala2.10.4的壓縮文件:https://www.scala-lang.org/download/2.10.4.html
解壓並配置環境變量(和配置jdk一樣)
新建SCALA_HOME
上個步驟完成後,編輯Path變量,在後面追加如下:
%SCALA_HOME%\bin;
打開cmd,輸入:scala - version 看是否顯示版本號,確定是否安裝成功
可通過scala命令進入Scala Shell.
2. Scala-IDEA
開發Scala程序, 可以去下載一個支持Scala的eclipse. 不推薦在現有的eclipse中安裝Scala插件的形式.
下載網址:http://scala-ide.org/download/sdk.html
3. IntelliJ IDEA中安裝Scala插件
打開idea,close項目後,點擊Configure->Plugins
搜索scala,點擊Install安裝
或者直接在settings的plugins中搜索scala
由於筆者之前以及安裝過, 因此提示的是卸載(Uninstall), 如果之前未安裝過會是一個綠色的√, 文字未Install.
4. IntelliJ IDEA-2017.3版本中創建Scala項目
創建Scala項目
設置項目名, 存放位置, JDK以及ScalaSDK
在src下新建包, 右鍵新建Scala Class
注意這裏選擇Object類型
Scala基礎
1. 數據類型
Scala中與Java相似的數據類型
數據類型 | 描述 | 範圍 |
---|---|---|
Byte | 8bit有符號數字 | -128~127 |
Short | 16bit有符號數字 | -32768~32767 |
Int | 32bit有符號數字 | -2147483648~2147483647 |
Long | 64bit有符號數字 | -263~263-1 |
Float | 32位IEEE754標準的單精度浮點數 | 1.4E-45~3.4028235E38 |
Double | 64位IEEE754標準的雙精度浮點數 | 4.9E-324~1.7976931348623157E308 |
Char | 16位Unicode字符 | U+0000~U+FFFF |
String | 字符串 | - |
Boolean | 布爾類型 | true/false |
Null | 空值或空引用, 是AnyRef的子類 | - |
Scala獨有的數據類型
數據類型 | 描述 |
---|---|
Unit | 表示無值, 同void |
Any | 所有類型的超類, 任何實例都屬於Any. Any的父類還是Object |
AnyRef | 所有引用類型的超類 |
AnyVal | 所有值類型的超類 |
Nothing | 表示沒有值, 所有其他類型的子類型, 該類型的對象不會報空指針異常 |
None | Option的子類,用於安全的函數返回值 |
some | Option的子類, 用於存儲元組數據(“hello”,“scala”), 類似於map,由一對小括號包括 |
數據類型類圖
2. 常量&變量
定義變量: var a: Int = 1
定義常量: val b: Int = 1
由於Scala支持類型推測, 因此常量或變量的類型一般省略, 上述定義可簡寫爲:
var a = 1
val b = 1
注意:
val修飾的常量一旦定義, 則其值不可修改. 如果修改, 會報error: reassignment to val.
Scala中語句之後可以不加分號’;’ , 自動根據換行符區分. 但是, 如果多條語句在一行, 必須加分號隔開.
3. 類&對象
創建類
class Person {
val name = "qibao"
val age = 22
def tell() = {
"My name is " + name + ". My age is " + age
}
}
val 聲明兩個常量, 不可修改.
def 聲明函數, 具體格式之後再說.
創建對象
object OnePerson {
def main(args: Array[String]): Unit = {
val person = new Person()
println(person.name + ":" + person.tell())
}
}
注意:
- 命名規則與Java保持一致即可, 包括類首字母都是大寫, 方法首字母小寫, 符合駝峯式命名法.
- Scala中class默認可傳參, 默認的傳參數就是默認的構造函數. 重載構造函數時, 必須調用默認構造函數.
//定義類時傳參
class Person(var name: String, var age: Int) {
var fcp = 0.0
//重載構造器, 參數不能有修飾符
def this(name: String, age:Int, facePower:Double) = {
//調用默認構造器
this(name,age)
fcp = facePower
}
def tell() = println(name + ":" + age + ":" + fcp)
}
- 在class修飾的類中, 如果參數用var來修飾, 那麼可以之間通過對象名.屬性來調用; 如果參數用val修飾, 則不能通過對象名.屬性來修改; 如果參數沒有修飾符, 則該屬性爲私有, 無法通過對象來調用.
object ScalaBase {
def main(args: Array[String]): Unit = {
var p1 = new Person("qb", 22, '男')
p1.name = "qibao"
// p1.age = 23 val定義無法修改
println(p1.age)
// println(p1.sex) 沒有修飾符,無法訪問
}
}
class Person(var name: String, val age: Int, sex: Char) {
def tell() = println(name + ":" + age)
}
- Scala中object修飾的單例對象, 不可傳遞參數. 在Scala中沒有static這一關鍵字, 所有靜態屬性和方法都需要放在object修飾的對象中.
- 創建object修飾的對象時, 不用new這一關鍵字; 創建class修飾的對象時, 使用new, 且在new的時候, 除了class中的方法不執行外, 其餘的都執行.
- 如果在同一個文件中, object對象和class類的名稱相同, 則這個對象就是這個類的伴生對象, 這個類就是這個對象的伴生類. 伴生對象和伴生類之間可以互相訪問私有變量.
- Scala中伴生對象+伴生類 = Java中某個類
- apply方法的使用: apply相當於Java中的靜態代碼塊, 創建對象時自動調用. 因此它要在object修飾的伴生對象中定義.
class Person(var name: String, var age: Int) {
def tell() = println(name + ":" + age)
}
object Person {
def apply(name: String, age: Int) = new Person(name, age)
}
定義之後再創建Person對象時, 可以省略new關鍵字
object ScalaBase {
def main(args: Array[String]): Unit = {
var p1 = new Person("qb", 22)
var p2 = Person("qb", 23)
}
}
4. 判斷語句
判斷語句與Java中的用法完全一致, 兩種形式 if-else和if-else if -else.
val age =18
if (age < 18 ){
println("no allow")
}else if (18 <= age && age <= 20){
println("allow with other")
}else{
println("allow self")
}
5.循環語句
5.1 to和until的使用
這倆個方法都會產生一個集合, 其中until前閉後開, to前後均閉.
比如說,1 until 10 返回1到9的數組, 1 to 10 返回1到10的數組.
println(1 to 5 )//打印 1, 2, 3, 4, 5
println(1.to(5))//與上面等價,打印 1, 2, 3, 4, 5
println(1 until 5 ) //不包含最後一個數,打印 1,2,3,4
println(1.until(5))//與上面等價
除按順序打印外, 還可設置步長.
println(1 to (10 ,2))//步長爲2,從1開始打印 ,1,3,5,7,9
println(1.to(10, 2))
println(1 until (10 ,3 ))//步長爲3,從1開始打印,打印1,4,7
5.2 for循環
- 通過索引
var list = List("hello", "scala", "spark")
for (index <- 0 until list.length) println(list(index))
- 增強for循環
for (elem <- list) println(elem)
在用Scala寫for循環時, 不需要指定循環變量的類型, Scala會自動推測出其類型, 同時給循環變量賦值使用"<-". Scala習慣來講, 如果判斷或循環語句只有一行, 一般把它們放在一行.
- 多層for循環
當有多層循環時, 可用分號’;'將循環變量隔開.
//打印九九乘法表
for (i <- 1 to 9; j <- 1 to 9) {
if (j <= i) print(i + "*" + j + "=" + i * j + "\t")
if (j == i) println()
}
- for循環中添加條件判斷
Scala的for循環, 還可以在循環變量設置的( ) 中添加條件判斷, 只需用分號’;'將它們隔開即可.
for (i <- 1 to 100; if i % 2 == 0) println(i)
for (i <- 1 to 10 ; if i % 2 == 0 ;if i == 4 ) println(i)
- yield關鍵詞自動封裝集合
在for循環中使用yield關鍵詞後, 能夠將符合要求的元素自動封裝到一個集合中.
//將1-20之間的偶數封裝到rest集合中
val rest = for (i <- 1 to 20; if i % 2 == 0) yield i
for (elem <- rest) println(elem)
5.3 while和do…while循環
Scala中while和do-while循環的用法與Java中也是完全一致的, 唯一一點需要注意的是Scala語法中沒有break語句, 因此想要跳出循環可以自定義一個布爾類型的標識符.
var flag = true
var index = 0
while (flag && index < 50){
index += 1
if (index == 20) flag = false
println(index)
}
通過上述幾個實例, 可以看出Scala語法的靈活性以及在寫Scala代碼時遵循的一些簡寫習慣, 之後在函數板塊會有更多簡寫規則, 更能體現Scala的靈活.
Scala函數
1. 函數定義
- 函數定義用def關鍵詞.
- 可有參也可無參,傳參時需指定參數類型.
- 函數可寫返回值類型也可不寫,Scala會自動推斷. 但有時候不能省必須寫,比如在遞歸函數或函數的返回值是函數類型的時候.
- Scala中函數有返回值時,return可寫可不寫. 如果不寫, 函數會把最後一行當做結果返回。當寫return時,必須要寫函數的返回值.
- 如果函數體只有一行,可以將方法名和函數體寫在一行.
- 傳遞給方法的參數可以在方法中使用,並且scala規定方法傳過來的參數爲val的,不是var的。
- 如果去掉方法體前面的等號,那麼這個方法返回類型必定是Unit的。這種說法無論方法體裏面什麼邏輯都成立,scala可以把任意類型轉換爲Unit.假設,裏面的邏輯最後返回了一個string,那麼這個返回值會被轉換成Unit,並且值會被丟棄。
def fun1(a: Int, b: Int): Int = {
return a + b;
}
def fun2(a: Int, b: Int) = {
a + b
}
def fun3(a: Int, b: Int) = a + b
從f1–>f3, 由Java編碼思想向Scala編碼思想的轉變. Scala追求的是一種高效, 快速的編碼習慣.
2. 遞歸函數
遞歸函數: 函數自身調用自身
//求一個數的階乘
def fun4(n: Int): Int = {
if (n == 1 || n == 0) 1
else n * fun4(n - 1)
}
需要注意的是, 遞歸函數在最後一次返回之前返回的是一個函數, 此時Scala無法推測其類型. 因此, 在Scala中寫遞歸函數時, 必須明確返回值類型, 否則會報Type mismatch的錯.
3. 帶默認值的函數
默認值函數: 函數在定義時, 指定某個或多個參數的值.
object ScalaFun1 {
def main(args: Array[String]): Unit = {
println(fun5());
println(fun5(1));
println(fun5(b=2));
println(fun5(1,2));
println(fun5_1(1));
println(fun5_2(b = 2));
}
//默認值函數
def fun5(a: Int = 10, b: Int = 20) = a + b
def fun5_1(a: Int, b: Int = 20) = a + b
def fun5_2(a: Int = 10, b: Int) = a + b
}
注意: 當參數有默認值時, 可以不指定, 也可指定. 但如果想要給不是第一個位置的參數指定值時, 需要明確參數的名稱.
4. 可變參數列表的函數
可變參數列表函數: 函數參數可以是一個也可以是多個. Java中使用…來指定可變參數列表方法, Scala中使用*.
def fun6(args: Double*) = {
var sum = 0.0
for (arg <- args) sum += arg
sum
}
注意: Scala中+=, -=前後的數值類型必須相同, Scala不會隱式類型轉換; +,- 可以前後類型不相同. 此外, 函數最後一行作爲返回值, for的返回是Unit(空), 因此需要另起一行返回sum.
補充一點: Scala中沒有++, --運算符.
5. 匿名函數
匿名函數: 沒有名字的函數. 既然沒有名字就無法調用, 因此可以將匿名函數賦給一個變量.
def main(args: Array[String]): Unit = {
println(fun7(1, 2));
}
val fun7 = (a: Int, b: Int) => a + b
注意:
- 匿名函數可以有參, 也可無參
- 匿名函數的參數和方法體要用 => 來連接
- 匿名函數不能顯示的聲明返回值類型
6. 嵌套函數
匿名函數: 函數體內又定義了一個函數.
def fun8 (n:Int) = {
def fun9(a:Int,b:Int):Int ={
if(a==1) b
else fun9(a-1,a*b)
}
fun9(n,1)
}
7. 偏應用函數
偏應用函數: 偏應用函數是一種表達式, 它實際是調用了其他函數. 只不過不需要提供函數所需的全部參數, 只需提供部分或不提供參數, 不提供的參數單獨指定.
//普通函數
def log(date: Date, content: String) = {
println("date" + date + "\tcontent" + content)
}
val date = new Date()
log(date,"log1")
log(date,"log2")
log(date,"log3")
//偏應用函數
val logBoundDate = log(date,_:String)
logBoundDate("log1_1")
logBoundDate("log2_1")
logBoundDate("log3_1")
分析代碼可知, 偏應用並不是真實的函數, 而是將某個函數的一個或多個參數指定後的表達式. 上述需求中, 需要傳入時間和內容作爲日誌, 但時間往往是不變的, 變化的只是內容, 因此可以使用偏應用函數來處理. 其中’_’ 代表佔位符, 指代第二個參數.
8. 高階函數
高階函數: 高階函數是指(1)函數的參數是函數; (2)函數的返回類型是函數; (3)函數的參數和返回類型是函數 的函數.
函數作爲參數或返回值進行傳遞時, 也是沒有名字的, 因此也相當於是匿名函數, 上文也說過, 匿名函數的參數和方法體之間用 => 進行連接.
- 函數的參數是函數
def highFun1(f: (Int) => Int, num: Int) = f(num)
def temF1(num: Int) = num + 1
//調用函數
println(highFun1(temF1, 1))
代碼分析: highFun1有兩個參數, 其中一個參數是函數, 函數的類型匹配參數爲Int, 返回爲Int的函數. 由此可以看出, Scala把函數當成對象在函數之間傳遞, 這也是Scala函數式編程的體現.
同時在使用高階函數時, 函數參數直接寫函數名, 不需要加括號(), 加括號說明是調用函數, 使用的是函數返回的結果, 直接寫函數名纔是將函數切切實實地作爲參數進行傳遞.
- 函數的返回類型是函數
def highFun2(num: Int): (Int, Int) => Double = {
def temF2(num1: Int, num2: Int): Double = {
num + num1 + num2
}
temF2
}
//調用函數
var fun = highFun2(1)
println(fun(1,1))
//上述兩行代碼也可簡寫爲
println(highFun2(1)(1,1))
代碼分析: highFun2有一個Int類型的參數, 返回一個(Int, Int) => Double格式的函數, 返回時可以像上述代碼中寫的一樣, 主動定義一個函數, 然後返回, 也可以直接在最後一行寫一個匿名函數返回. 如下:
def highFun3(num: Int): (Int, Int) => Double = {
(num1: Int, num2: Int) => num1 + num2 + num
}
調用highFun2時, 它返回一個函數, 這就相當於匿名函數, 可以通過一個變量來接收這個函數, 然後再通過給這個變量設置參數的形式得到返回函數的值.
- 函數的參數和返回類型都是函數
def highFun4(f: (Int,Int) => Int, num1: Int): (Int) => Double = {
(num: Int) => num + f(num1,1)
}
//調用函數
var fun = highFun4((a:Int,b:Int)=>a+b,1)
var res0 = fun(1)
println(res0)
//上述可簡化
var res1 = highFun4((a:Int,b:Int)=>a+b,1)(1)
println(res1)
//如果匿名函數的參數在方法體中只使用了一次 那麼可以寫成_表示
var res2 = highFun4(_+_,2)(2)
println(res2)
highFun4的參數類型和返回類型不用再說了, 主要是函數調用時代碼簡化的過程, highFun4的匿名函數參數已經指定該函數有兩個參數(這句話比較拗口, 建議讀者慢慢領會), 在調用highFun4時, 如果這個函數參數的兩個參數只使用一次, 那麼可以用 _ 來表示這些參數.
Scala學習地越深入, 越容易發現Scala許多操作類似jQuery中的鏈式操作, 而且有些內容能省則省, 這就於人類日常交流類似, 雙方都知道的事情, 就沒有必要每次說話時再點出來.
9. 柯里化函數
柯里化函數: 柯里化函數實質上是一類高階函數的簡化.
先來看這一類高階函數:
def highFun5(num: Int): (Int) => Int = {
def fun(a: Int) = num + a
fun
}
//調用時,可以將兩個參數寫一行
println(highFun5(1)(2))
再來看柯里化函數:
def klhFun(a: Int)(b: Int) = a + b
//調用方式跟上述代碼一致
println(klhFun(1)(2))
滿足上述樣式的函數就是柯里化函數, 再比如:
def fun7(a :Int,b:Int)(c:Int,d:Int) = {
a+b+c+d
}
println(fun7(1,2)(3,4))