第1章 Scala入門
1.1 概述
1.1.1 為什么學習Scala
1.1.2 Scala發展歷史
1.1.3 Scala和Java關系
一般來說,學Scala的人,都會Java,而Scala是基於Java的,因此我們需要將Scala和Java以及JVM之間的關系搞清楚,否則學習Scala你會蒙圈。
1.1.4 Scala語言特點
1.2 Scala環境搭建
1)安裝步驟
(1)首先確保JDK1.8安裝成功
(2)下載對應的Scala安裝文件scala-2.11.8.zip
(3)解壓scala-2.11.8.zip,我這里解壓到E:\02_software
(4)配置Scala的環境變量
注意1:解壓路徑不能有任何中文路徑,最好不要有空格。
注意2:環境變量要大寫SCALA_HOME
2)測試
需求:定義兩個變量n和n2,並計算n和n2兩個變量的和。
步驟
(1)在鍵盤上同時按win+r鍵,並在運行窗口輸入cmd命令
(2)輸入Scala並按回車鍵,啟動Scala環境。然后定義兩個變量,並計算求和。
1.3 Scala插件安裝
默認情況下IDEA不支持Scala的開發,需要安裝Scala插件。
1)插件離線安裝步驟
(1)建議將該插件scala-intellij-bin-2017.2.6.zip文件,放到Scala的安裝目錄E:\02_software\scala-2.11.8下,方便管理。
(2)打開IDEA,在左上角找到File->在下拉菜單中點擊Setting... ->點擊Plugins->點擊右下角Install plugin from disk…,找到插件存儲路徑E:\02_software\scala-2.11.8\scala-intellij-bin-2017.2.6.zip,最后點擊ok。
2)插件在線安裝(可選)
(1)在搜索插件框里面輸入Scala->點擊Install->點擊ok->點擊apply。
(2)重啟IDEA,再次來到Scala插件頁面,已經變成Uninstall。
1.4 HelloWorld案例
1.4.1 創建IDEA項目工程
1)打開IDEA->點擊左側的Flie->選擇New->選擇Project…
2)創建一個Maven工程,並點擊next
3)GroupId輸入com.atguigu->ArtifactId輸入scala0513->點擊next->點擊Finish
注意:工程存儲路徑一定不要有中文和空格。
4)指定項目工作目錄空間
5)默認下,Maven不支持Scala的開發,需要引入Scala框架。
在scala0513項目上,點擊右鍵-> Add Framework Support... ->選擇Scala->點擊OK
注意:如果是第一次引入框架,Use libary看不到,需要選擇你的Scala安裝目錄,然后工具就會自動識別,就會顯示user libary。
6)創建項目的源文件目錄
右鍵點擊main目錄->New->點擊Diretory -> 寫個名字(比如scala)。
右鍵點擊scala目錄->Mark Directory as->選擇Sources root,觀察文件夾顏色發生變化。
7)在scala包下,創建包com.atguigu.chapter01包名和Hello類名,
右鍵點擊scala目錄->New->Package->輸入com.atguigu.chapter01->點擊OK。
右鍵點擊com.atguigu.chapter01->New->Scala Class->Kind項選擇Object->Name項輸入Hello。
8)編寫輸出Hello Scala案例
在類中中輸入main,然后回車可以快速生成main方法;
在main方法中輸入println("hello scala")
運行后,觀察控制台打印輸出:
hello scala
hello scala
說明:Java中部分代碼也是可以在Scala中運行。
1.4.2 class和object說明
對第一個程序進行說明
1.4.3 Scala程序反編譯
1)在項目的target目錄Hello文件上點擊右鍵->Show in Explorer->看到object底層生成Hello$.class和Hello.class兩個文件
2)采用Java反編譯工具jd-gui.exe反編譯代碼,將Hello.class拖到jd-gui.exe頁面
1.5 關聯Scala源碼
在使用Scala過程中,為了搞清楚Scala底層的機制,需要查看源碼,下面看看如何關聯和查看Scala的源碼包。
1)查看源碼
例如查看Array源碼。按住ctrl鍵->點擊Array->右上角出現Attach Soures…
2)關聯源碼
(1)將我們的源碼包scala-sources-2.12.4.tar.gz拷貝到E:\02_software\scala-2.11.8\lib文件夾下,並解壓為scala-sources-2.12.4文件夾
(2)點擊Attach Sources…->選擇E:\02_software\scala-2.11.8\lib\scala-sources-2.12.4,這個文件夾,就可以看到源碼了
1.6 官方編程指南
1)在線查看:https://www.scala-lang.org/
2)離線查看:解壓scala-docs-2.11.8.zip,可以獲得Scala的API操作。
第2章 變量和數據類型
2.1 注釋
Scala注釋使用和Java完全一樣。
注釋是一個程序員必須要具有的良好編程習慣。將自己的思想通過注釋先整理出來,再用代碼去體現。
1)基本語法
(1)單行注釋://
(2)多行注釋:/* */
(3)文檔注釋:/**
*
*/
2)案例實操
package com.atguigu.chapter02
object TestNotes {
def main(args: Array[String]): Unit = {
//(1)單行注釋://
println("dalang")
//(2)多行注釋:/* */
/*
println("dalang")
println("jinlian")
*/
//(3)文檔注釋:/**
//*
//*/
/**
* println("qingge")
println("qingge")
println("qingge")
*/
}
}
3) 代碼規范
(1)使用一次tab操作,實現縮進,默認整體向右邊移動,用shift+tab整體向左移
(2)或者使用ctrl + alt + L來進行格式化
(3)運算符兩邊習慣性各加一個空格。比如:2 + 4 * 5。
(4)一行最長不超過80個字符,超過的請使用換行展示,盡量保持格式優雅
2.2 變量和常量(重點)
常量:在程序執行的過程中,其值不會被改變的變量
0)回顧:Java變量和常量語法
變量類型 變量名稱 = 初始值 int a = 10
final常量類型 常量名稱 = 初始值 final int b = 20
1)基本語法
var 變量名 [: 變量類型] = 初始值 var i:Int = 10
val 常量名 [: 常量類型] = 初始值 val j:Int = 20
注意:能用常量的地方不用變量
2)案例實操
(1)聲明變量時,類型可以省略,編譯器自動推導,即類型推導
(2)類型確定后,就不能修改,說明Scala是強數據類型語言。
(3)變量聲明時,必須要有初始值
(4)在聲明/定義一個變量時,可以使用var或者val來修飾,var修飾的變量可改變,val修飾的變量不可改。
package com.atguigu.chapter02
object TestVar {
def main(args: Array[String]): Unit = {
//(1)聲明變量時,類型可以省略,編譯器自動推導,即類型推導
var age = 18
age = 30
//(2)類型確定后,就不能修改,說明Scala是強數據類型語言。
// age = "tom" // 錯誤
//(3)變量聲明時,必須要有初始值
// var name //錯誤
//(4)在聲明/定義一個變量時,可以使用var或者val來修飾,var修飾的變量可改變,val修飾的變量不可改。
var num1 = 10 // 可變
val num2 = 20 // 不可變
num1 = 30 // 正確
//num2 = 100 //錯誤,因為num2是val修飾的
}
}
(5)var修飾的對象引用可以改變,val修飾的對象則不可改變,但對象的狀態(值)卻是可以改變的。(比如:自定義對象、數組、集合等等)
object TestVar {
def main(args: Array[String]): Unit = {
// p1是var修飾的,p1的屬性可以變,而且p1本身也可以變
var p1 = new Person()
p1.name = "dalang"
p1 = null
// p2是val修飾的,那么p2本身就不可變(即p2的內存地址不能變),但是,p2的屬性是可以變,因為屬性並沒有用val修飾。
val p2 = new Person()
p2.name="jinlian"
// p2 = null // 錯誤的,因為p2是val修飾的
}
}
class Person{
var name : String = "jinlian"
}
2.3 標識符的命名規范
Scala對各種變量、方法、函數等命名時使用的字符序列稱為標識符。即:凡是自己可以起名字的地方都叫標識符。
1)命名規則
Scala中的標識符聲明,基本和Java是一致的,但是細節上會有所變化,有以下四種規則:
(1)以字母或者下划線開頭,后接字母、數字、下划線
(2)以操作符開頭,且只包含操作符(+ - * / # !等)
(3)用反引號`....`包括的任意字符串,即使是Scala關鍵字(39個)也可以
- package, import, class, object, trait, extends, with, type, for
- private, protected, abstract, sealed, final, implicit, lazy, override
- try, catch, finally, throw
- if, else, match, case, do, while, for, return, yield
- def, val, var
- this, super
- new
- true, false, null
2)案例實操
需求:判斷hello、Hello12、1hello、h-b、x h、h_4、_ab、Int、_、+*-/#!、+*-/#!1、if、`if`,這些名字是否合法。
object TestName {
def main(args: Array[String]): Unit = {
// (1)以字母或者下划線開頭,后接字母、數字、下划線
var hello: String = "" // ok
var Hello12: String = "" // ok
var 1hello: String = "" // error 數字不能開頭
var h-b: String = "" // error 不能用-
var x h: String = "" // error 不能有空格
var h_4: String = "" // ok
var _ab: String = "" // ok
var Int: String = "" // ok 因為在Scala中Int是預定義的字符,不是關鍵字,但不推薦
var _: String = "hello" // ok 單獨一個下划線不可以作為標識符,因為_被認為是一個方法
println(_)
//(2)以操作符開頭,且只包含操作符(+ - * / # !等)
var +*-/#! : String = "" // ok
var +*-/#!1 : String = "" // error 以操作符開頭,必須都是操作符
//(3)用反引號`....`包括的任意字符串,即使是Scala關鍵字(39個)也可以
var if : String = "" // error 不能用關鍵字
var `if` : String = "" // ok 用反引號`....`包括的任意字符串,包括關鍵字
}
}
2.4 字符串輸出
1)基本語法
(1)字符串,通過+號連接
(2)printf用法:字符串,通過%傳值。
(3)字符串模板(插值字符串):通過$獲取變量值
2)案例實操
package com.atguigu.chapter02
object TestCharType {
def main(args: Array[String]): Unit = {
var name: String = "jinlian"
var age: Int = 18
//(1)字符串,通過+號連接
println(name + " " + age)
//(2)printf用法字符串,通過%傳值。
printf("name=%s age=%d\n", name, age)
//(3)字符串,通過$引用
//多行字符串,在Scala中,利用三個雙引號包圍多行字符串就可以實現。//輸入的內容,帶有空格、\t之類,導致每一行的開始位置不能整潔對齊。
//應用scala的stripMargin方法,在scala中stripMargin默認是“|”作為連接符,//在多行換行的行頭前面加一個“|”符號即可。
val s =
"""
|select
| name,
| age
|from user
|where name="zhangsan"
""".stripMargin
println(s)
//如果需要對變量進行運算,那么可以加${}
val s1 =
s"""
|select
| name,
| age
|from user
|where name="$name" and age=${age+2}
""".stripMargin
println(s1)
val s2 = s"name=$name"
println(s2)
}
}
2.5 鍵盤輸入
在編程中,需要接收用戶輸入的數據,就可以使用鍵盤輸入語句來獲取。
1)基本語法
StdIn.readLine()、StdIn.readShort()、StdIn.readDouble()
2)案例實操
需求:可以從控制台接收用戶信息,【姓名,年齡,薪水】。
import scala.io.StdIn
object TestInput {
def main(args: Array[String]): Unit = {
// 1 輸入姓名
println("input name:")
var name = StdIn.readLine()
// 2 輸入年齡
println("input age:")
var age = StdIn.readShort()
// 3 輸入薪水
println("input sal:")
var sal = StdIn.readDouble()
// 4 打印
println("name=" + name)
println("age=" + age)
println("sal=" + sal)
}
}
2.6 數據類型(重點)
2.7 整數類型(Byte、Short、Int、Long)
Scala的整數類型就是用於存放整數值的,比如12,30,3456等等。
1)整型分類
數據類型 |
描述 |
Byte [1] |
8位有符號補碼整數。數值區間為 -128 到 127 |
Short [2] |
16位有符號補碼整數。數值區間為 -32768 到 32767 |
Int [4] |
32位有符號補碼整數。數值區間為 -2147483648 到 2147483647 |
Long [8] |
64位有符號補碼整數。數值區間為 -9223372036854775808 到 9223372036854775807 = 2的(64-1)次方-1 |
2)案例實操
(1)Scala各整數類型有固定的表示范圍和字段長度,不受具體操作的影響,以保證Scala程序的可移植性。
object TestDataType {
def main(args: Array[String]): Unit = {
// 正確
var n1:Byte = 127
var n2:Byte = -128
// 錯誤
// var n3:Byte = 128
// var n4:Byte = -129
}
}
(2)Scala的整型,默認為Int型,聲明Long型,須后加‘l’或‘L’
object TestDataType {
def main(args: Array[String]): Unit = {
var n5 = 10
println(n5)
var n6 = 9223372036854775807L
println(n6)
}
}
(3)Scala程序中變量常聲明為Int型,除非不足以表示大數,才使用Long
2.8 浮點類型(Float、Double)
Scala的浮點類型可以表示一個小數,比如123.4f,7.8,0.12等等。
1)浮點型分類
數據類型 |
描述 |
Float [4] |
32 位, IEEE 754標准的單精度浮點數 |
Double [8] |
64 位 IEEE 754標准的雙精度浮點數 |
2)案例實操
Scala的浮點型常量默認為Double型,聲明Float型常量,須后加‘f’或‘F’。
object TestDataType {
def main(args: Array[String]): Unit = {
// 建議,在開發中需要高精度小數時,請選擇Double
var n7 = 2.2345678912f
var n8 = 2.2345678912
println("n7=" + n7)
println("n8=" + n8)
}
}
//運行的結果
n7=2.2345679
n8=2.2345678912
2.9 字符類型(Char)
1)基本說明
字符類型可以表示單個字符,字符類型是Char。
2)案例實操
(1)字符常量是用單引號 ' ' 括起來的單個字符。
(2)\t :一個制表位,實現對齊的功能
(3)\n :換行符
(4)\\ :表示\
(5)\" :表示"
object TestCharType {
def main(args: Array[String]): Unit = {
//(1)字符常量是用單引號 ' ' 括起來的單個字符。
var c1: Char = 'a'
println("c1=" + c1)
//注意:這里涉及自動類型提升,其實編譯器可以自定判斷是否超出范圍,
//不過idea提示報錯
var c2:Char = 'a' + 1
println(c2)
//(2)\t :一個制表位,實現對齊的功能
println("姓名\t年齡")
//(3)\n :換行符
println("西門慶\n潘金蓮")
//(4)\\ :表示\
println("c:\\島國\\avi")
//(5)\" :表示"
println("同學們都說:\"大海哥最帥\"")
}
}
2.10 布爾類型:Boolean
1)基本說明
(1)布爾類型也叫Boolean類型,Booolean類型數據只允許取值true和false
(2)boolean類型占1個字節。
2)案例實操
object TestBooleanType {
def main(args: Array[String]): Unit = {
var isResult : Boolean = false
var isResult2 : Boolean = true
}
}
2.11 Unit類型、Null類型和Nothing類型(重點)
1)基本說明
數據類型 |
描述 |
Unit |
表示無值,和其他語言中void等同。用作不返回任何結果的方法的結果類型。Unit只有一個實例值,寫成()。 |
Null |
null , Null 類型只有一個實例值null |
Nothing |
Nothing類型在Scala的類層級最低端;它是任何其他類型的子類型。 當一個函數,我們確定沒有正常的返回值,可以用Nothing來指定返回類型,這樣有一個好處,就是我們可以把返回的值(異常)賦給其它的函數或者變量(兼容性) |
2)案例實操
(1)Unit類型用來標識過程,也就是沒有明確返回值的函數。
由此可見,Unit類似於Java里的void。Unit只有一個實例——( ),這個實例也沒有實質意義
object TestSpecialType {
def main(args: Array[String]): Unit = {
def sayOk : Unit = {// unit表示沒有返回值,即void
}
println(sayOk)
}
}
(2)Null類只有一個實例對象,Null類似於Java中的null引用。Null可以賦值給任意引用類型(AnyRef),但是不能賦值給值類型(AnyVal)
object TestDataType {
def main(args: Array[String]): Unit = {
//null可以賦值給任意引用類型(AnyRef),但是不能賦值給值類型(AnyVal)
var cat = new Cat();
cat = null // 正確
var n1: Int = null // 錯誤
println("n1:" + n1)
}
}
class Cat {
}
(3)Nothing,可以作為沒有正常返回值的方法的返回類型,非常直觀的告訴你這個方法不會正常返回,而且由於Nothing是其他任意類型的子類,他還能跟要求返回值的方法兼容。
object TestSpecialType {
def main(args: Array[String]): Unit = {
def test() : Nothing={
throw new Exception()
}
test
}
}
2.12 類型轉換
擴展Java面試題(隱式類型轉換):
2.12.1 數值類型自動轉換
當Scala程序在進行賦值或者運算時,精度小的類型自動轉換為精度大的數值類型,這個就是自動類型轉換(隱式轉換)。數據類型按精度(容量)大小排序為:
1)基本說明
(1)自動提升原則:有多種類型的數據混合運算時,系統首先自動將所有數據轉換成精度大的那種數據類型,然后再進行計算。
(2)把精度大的數值類型賦值給精度小的數值類型時,就會報錯,反之就會進行自動類型轉換。
(3)(byte,short)和char之間不會相互自動轉換。
(4)byte,short,char他們三者可以計算,在計算時首先轉換為int類型。
2)案例實操
object TestValueTransfer {
def main(args: Array[String]): Unit = {
//(1)自動提升原則:有多種類型的數據混合運算時,系統首先自動將所有數據轉換成精度大的那種數值類型,然后再進行計算。
var n = 1 + 2.0
println(n) // n 就是Double
//(2)把精度大的數值類型賦值給精度小的數值類型時,就會報錯,反之就會進行自動類型轉換。
var n2 : Double= 1.0
//var n3 : Int = n2 //錯誤,原因不能把高精度的數據直接賦值和低精度。
//(3)(byte,short)和char之間不會相互自動轉換。
var n4 : Byte = 1
//var c1 : Char = n4 //錯誤
var n5:Int = n4
//(4)byte,short,char他們三者可以計算,在計算時首先轉換為int類型。
var n6 : Byte = 1
var c2 : Char = 1
// var n : Short = n6 + c2 //當n6 + c2 結果類型就是int
// var n7 : Short = 10 + 90 //錯誤
}
}
注意:Scala還提供了非常強大的隱式轉換機制(隱式函數,隱式類等),我們放在高級部分專門用一個章節來講解。
2.12.2 強制類型轉換
1)基本說明
自動類型轉換的逆過程,將精度大的數值類型轉換為精度小的數值類型。使用時要加上強制轉函數,但可能造成精度降低或溢出,格外要注意。
Java : int num = (int)2.5
Scala : var num : Int = 2.7.toInt
2)案例實操
(1)將數據由高精度轉換為低精度,就需要使用到強制轉換
(2)強轉符號只針對於最近的操作數有效,往往會使用小括號提升優先級
object TestForceTransfer {
def main(args: Array[String]): Unit = {
//(1)將數據由高精度轉換為低精度,就需要使用到強制轉換
var n1: Int = 2.5.toInt // 這個存在精度損失
//(2)強轉符號只針對於最近的操作數有效,往往會使用小括號提升優先級
var r1: Int = 10 * 3.5.toInt + 6 * 1.5.toInt // 10 *3 + 6*1 = 36
var r2: Int = (10 * 3.5 + 6 * 1.5).toInt // 44.0.toInt = 44
println("r1=" + r1 + " r2=" + r2)
}
}
2.12.3 數值類型和String類型間轉換
1)基本說明
在程序開發中,我們經常需要將基本數值類型轉成String類型。或者將String類型轉成基本數值類型。
2)案例實操
(1)基本類型轉String類型(語法:將基本類型的值+"" 即可)
(2)String類型轉基本數值類型(語法:s1.toInt、s1.toFloat、s1.toDouble、s1.toByte、s1.toLong、s1.toShort)
object TestStringTransfer {
def main(args: Array[String]): Unit = {
//(1)基本類型轉String類型(語法:將基本類型的值+"" 即可)
var str1 : String = true + ""
var str2 : String = 4.5 + ""
var str3 : String = 100 +""
//(2)String類型轉基本數值類型(語法:調用相關API)
var s1 : String = "12"
var n1 : Byte = s1.toByte
var n2 : Short = s1.toShort
var n3 : Int = s1.toInt
var n4 : Long = s1.toLong
}
}
(3)注意事項
在將String類型轉成基本數值類型時,要確保String類型能夠轉成有效的數據,比如我們可以把"123",轉成一個整數,但是不能把"hello"轉成一個整數。
var n5:Int = "12.6".toInt會出現NumberFormatException異常。
擴展面試題
第3章 運算符
Scala運算符的使用和Java運算符的使用基本相同,只有個別細節上不同。
3.1 算術運算符
1)基本語法
運算符 |
運算 |
范例 |
結果 |
+ |
正號 |
+3 |
3 |
- |
負號 |
b=4; -b |
-4 |
+ |
加 |
5+5 |
10 |
- |
減 |
6-4 |
2 |
* |
乘 |
3*4 |
12 |
/ |
除 |
5/5 |
1 |
% |
取模(取余) |
7%5 |
2 |
+ |
字符串相加 |
“He”+”llo” |
“Hello” |
(1)對於除號“/”,它的整數除和小數除是有區別的:整數之間做除法時,只保留整數部分而舍棄小數部分。
(2)對一個數取模a%b,和Java的取模規則一樣。
2)案例實操
object TestArithmetic {
def main(args: Array[String]): Unit = {
//(1)對於除號“/”,它的整數除和小數除是有區別的:整數之間做除法時,只保留整數部分而舍棄小數部分。
var r1: Int = 10 / 3 // 3
println("r1=" + r1)
var r2: Double = 10 / 3 // 3.0
println("r2=" + r2)
var r3: Double = 10.0 / 3 // 3.3333
println("r3=" + r3)
println("r3=" + r3.formatted("%.2f")) // 含義:保留小數點2位,使用四舍五入
//(2)對一個數取模a%b,和Java的取模規則一樣。
var r4 = 10 % 3 // 1
println("r4=" + r4)
}
}
3.2 關系運算符(比較運算符)
1)基本語法
運算符 |
運算 |
范例 |
結果 |
== |
相等於 |
4==3 |
false |
!= |
不等於 |
4!=3 |
true |
< |
小於 |
4<3 |
false |
> |
大於 |
4>3 |
true |
<= |
小於等於 |
4<=3 |
false |
>= |
大於等於 |
4>=3 |
true |
2)案例實操
(1)需求1:
object TestRelation {
def main(args: Array[String]): Unit = {
// 測試:>、>=、<=、<、==、!=
var a: Int = 2
var b: Int = 1
println(a > b) // true
println(a >= b) // true
println(a <= b) // false
println(a < b) // false
println("a==b" + (a == b)) // false
println(a != b) // true
}
}
(2)需求2:Java和Scala中關於==的區別
Java:
==比較兩個變量本身的值,即兩個對象在內存中的首地址;
equals比較字符串中所包含的內容是否相同。
public static void main(String[] args) {
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
輸出結果:
false
true
Scala:==更加類似於Java中的equals,參照jd工具
def main(args: Array[String]): Unit = {
val s1 = "abc"
val s2 = new String("abc")
println(s1 == s2)
println(s1.eq(s2))
}
輸出結果:
true
false
3.3 邏輯運算符
1)基本語法
用於連接多個條件(一般來講就是關系表達式),最終的結果也是一個Boolean值。
假定:變量A為true,B為false
運算符 |
描述 |
實例 |
&& |
邏輯與 |
(A && B) 運算結果為 false |
|| |
邏輯或 |
(A || B) 運算結果為 true |
! |
邏輯非 |
!(A && B) 運算結果為 true |
2)案例實操
object TestLogic {
def main(args: Array[String]): Unit = {
// 測試:&&、||、!
var a = true
var b = false
println("a&&b=" + (a && b)) // a&&b=false
println("a||b=" + (a || b)) // a||b=true
println("!(a&&b)=" + (!(a && b))) // !(a&&b)=true
}
}
擴展避免邏輯與空指針異常
isNotEmpty(String s){
//如果邏輯與,s為空,會發生空指針
return s!=null && !"".equals(s.trim());
}
3.4 賦值運算符
1)基本語法
賦值運算符就是將某個運算后的值,賦給指定的變量。
運算符 |
描述 |
實例 |
= |
簡單的賦值運算符,將一個表達式的值賦給一個左值 |
C = A + B 將 A + B 表達式結果賦值給 C |
+= |
相加后再賦值 |
C += A 等於 C = C + A |
-= |
相減后再賦值 |
C -= A 等於 C = C - A |
*= |
相乘后再賦值 |
C *= A 等於 C = C * A |
/= |
相除后再賦值 |
C /= A 等於 C = C / A |
%= |
求余后再賦值 |
C %= A 等於 C = C % A |
<<= |
左移后賦值 |
C <<= 2等於 C = C << 2 |
>>= |
右移后賦值 |
C >>= 2 等於 C = C >> 2 |
&= |
按位與后賦值 |
C &= 2 等於 C = C & 2 |
^= |
按位異或后賦值 |
C ^= 2 等於 C = C ^ 2 |
|= |
按位或后賦值 |
C |= 2 等於 C = C | 2 |
注意:Scala中沒有++、--操作符,可以通過+=、-=來實現同樣的效果;
擴展:+=沒有Java中的強轉功能 ;階乘- -;++在后面試題
2)案例實操
object TestAssignment {
def main(args: Array[String]): Unit = {
var r1 = 10
r1 += 1 // 沒有++
r1 -= 2 // 沒有--
}
}
3.5 位運算符
1)基本語法
下表中變量 a 為 60,b 為 13。
運算符 |
描述 |
實例 |
& |
按位與運算符 |
(a & b) 輸出結果 12 ,二進制解釋: 0000 1100 |
| |
按位或運算符 |
(a | b) 輸出結果 61 ,二進制解釋: 0011 1101 |
^ |
按位異或運算符 |
(a ^ b) 輸出結果 49 ,二進制解釋: 0011 0001 |
~ |
按位取反運算符 |
(~a ) 輸出結果 -61 ,二進制解釋: 1100 0011, 在一個有符號二進制數的補碼形式。 |
<< |
左移動運算符 |
a << 2 輸出結果 240 ,二進制解釋: 0011 0000 |
>> |
右移動運算符 |
a >> 2 輸出結果 15 ,二進制解釋: 0000 1111 |
>>> |
無符號右移 |
a >>>2 輸出結果 15, 二進制解釋: 0000 1111 |
2)案例實操
object TestPosition {
def main(args: Array[String]): Unit = {
// 測試:1000 << 1 =>10000
var n1 :Int =8
n1 = n1 << 1
println(n1)
}
}
3.6 Scala運算符本質
在Scala中其實是沒有運算符的,所有運算符都是方法。
1)當調用對象的方法時,點.可以省略
2)如果函數參數只有一個,或者沒有參數,()可以省略
object TestOpt {
def main(args: Array[String]): Unit = {
// 標准的加法運算
val i:Int = 1.+(1)
// (1)當調用對象的方法時,.可以省略
val j:Int = 1 + (1)
// (2)如果函數參數只有一個,或者沒有參數,()可以省略
val k:Int = 1 + 1
println(1.toString())
println(1 toString())
println(1 toString)
}
}
第4章 流程控制
4.1 分支控制if-else
讓程序有選擇的的執行,分支控制有三種:單分支、雙分支、多分支
4.1.1 單分支
1)基本語法
if (條件表達式) {
執行代碼塊
}
說明:當條件表達式為ture時,就會執行{ }的代碼。
2)案例實操
需求:輸入人的年齡,如果該同志的年齡小於18歲,則輸出“童年”
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age:")
var age = StdIn.readShort()
if (age < 18){
println("童年")
}
}
}
4.1.2 雙分支
1)基本語法
if (條件表達式) {
執行代碼塊1
} else {
執行代碼塊2
}
2)案例實操
需求:輸入年齡,如果年齡小於18歲,則輸出“童年”。否則,輸出“成年”。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age:")
var age = StdIn.readShort()
if (age < 18){
println("童年")
}else{
println("成年")
}
}
}
4.1.3 多分支
1)基本語法
if (條件表達式1) {
執行代碼塊1
}
else if (條件表達式2) {
執行代碼塊2
}
……
else {
執行代碼塊n
}
2)案例實操
(1)需求1:需求:輸入年齡,如果年齡小於18歲,則輸出“童年”。如果年齡大於等於18且小於等於30,則輸出“中年”,否則,輸出“老年”。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
if (age < 18){
println("童年")
}else if(age>=18 && age<30){
println("中年")
}else{
println("老年")
}
}
}
(2)需求2:Scala中if else表達式其實是有返回值的,具體返回值取決於滿足條件的代碼體的最后一行內容。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res :String = if (age < 18){
"童年"
}else if(age>=18 && age<30){
"中年"
}else{
"老年"
}
println(res)
}
}
(3)需求3:Scala中返回值類型不一致,取它們共同的祖先類型。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res:Any = if (age < 18){
"童年"
}else if(age>=18 && age<30){
"中年"
}else{
100
}
println(res)
}
}
(4)需求4:Java中的三元運算符可以用if else實現
如果大括號{}內的邏輯代碼只有一行,大括號可以省略。如果省略大括號,if只對最近的一行邏輯代碼起作用。
object TestIfElse {
def main(args: Array[String]): Unit = {
// Java
// int result = flag?1:0
// Scala
println("input age")
var age = StdIn.readInt()
val res:Any = if (age < 18) "童年" else "成年"
"不起作用"
println(res)
}
}
4.2 嵌套分支
在一個分支結構中又完整的嵌套了另一個完整的分支結構,里面的分支的結構稱為內層。分支外面的分支結構稱為外層分支。嵌套分支不要超過3層。
1)基本語法
if(){
if(){
}else{
}
}
2)案例實操
需求:如果輸入的年齡小於18,返回“童年”。如果輸入的年齡大於等於18,需要再判斷:如果年齡大於等於18且小於30,返回“中年”;如果其他,返回“老年”。
object TestIfElse {
def main(args: Array[String]): Unit = {
println("input age")
var age = StdIn.readInt()
val res :String = if (age < 18){
"童年"
}else {
if(age>=18 && age<30){
"中年"
}else{
"老年"
}
}
println(res)
}
}
4.3 Switch分支結構
在Scala中沒有Switch,而是使用模式匹配來處理。
模式匹配涉及到的知識點較為綜合,因此我們放在后面講解。
4.4 For循環控制
Scala也為for循環這一常見的控制結構提供了非常多的特性,這些for循環的特性被稱為for推導式或for表達式。
4.4.1 范圍數據循環(To)
1)基本語法
for(i <- 1 to 3){
print(i + " ")
}
println()
(1)i 表示循環的變量,<- 規定to
(2)i 將會從 1-3 循環,前后閉合
2)案例實操
需求:輸出5句 "宋宋,告別海狗人參丸吧"
object TestFor {
def main(args: Array[String]): Unit = {
for(i <- 1 to 5){
println("宋宋,告別海狗人參丸吧"+i)
}
}
}
4.4.2 范圍數據循環(Until)
1)基本語法
for(i <- 1 until 3) {
print(i + " ")
}
println()
(1)這種方式和前面的區別在於i是從1到3-1
(2)即使前閉合后開的范圍
2)案例實操
需求:輸出5句 "宋宋,告別海狗人參丸吧"
object TestFor {
def main(args: Array[String]): Unit = {
for(i <- 1 until 5 + 1){
println("宋宋,告別海狗人參丸吧" + i)
}
}
}
4.4.3 循環守衛
1)基本語法
for(i <- 1 to 3 if i != 2) {
print(i + " ")
}
println()
說明:
(1)循環守衛,即循環保護式(也稱條件判斷式,守衛)。保護式為true則進入循環體內部,為false則跳過,類似於continue。
(2)上面的代碼等價
for (i <- 1 to 3){
if (i != 2) {
print(i + " ")
}
}
2)案例實操
需求:輸出1到5中,不等於3的值
object TestFor {
def main(args: Array[String]): Unit = {
for (i <- 1 to 5 if i != 3) {
println(i + "宋宋")
}
}
}
4.4.4 循環步長
1)基本語法
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
說明:by表示步長
2)案例實操
需求:輸出1到10以內的所有奇數
for (i <- 1 to 10 by 2) {
println("i=" + i)
}
輸出結果
i=1
i=3
i=5
i=7
i=9
4.4.5 嵌套循環
1)基本語法
for(i <- 1 to 3; j <- 1 to 3) {
println(" i =" + i + " j = " + j)
}
說明:沒有關鍵字,所以范圍后一定要加;來隔斷邏輯
2)基本語法
上面的代碼等價
for (i <- 1 to 3) {
for (j <- 1 to 3) {
println("i =" + i + " j=" + j)
}
}
4.4.6 引入變量
1)基本語法
for(i <- 1 to 3; j = 4 - i) {
println("i=" + i + " j=" + j)
}
說明:
(1)for推導式一行中有多個表達式時,所以要加 ; 來隔斷邏輯
(2)for推導式有一個不成文的約定:當for推導式僅包含單一表達式時使用圓括號,當包含多個表達式時,一般每行一個表達式,並用花括號代替圓括號,如下
for {
i <- 1 to 3
j = 4 - i
} {
println("i=" + i + " j=" + j)
}
2)案例實操
上面的代碼等價於
for (i <- 1 to 3) {
var j = 4 - i
println("i=" + i + " j=" + j)
}
擴展練習
4.4.7 循環返回值
1)基本語法
val res = for(i <- 1 to 10) yield i
println(res)
說明:將遍歷過程中處理的結果返回到一個新Vector集合中,使用yield關鍵字。
注意:開發中很少使用。
2)案例實操
需求:將原數據中所有值乘以2,並把數據返回到一個新的集合中。
object TestFor {
def main(args: Array[String]): Unit = {
var res = for(i <-1 to 10) yield {
i * 2
}
println(res)
}
}
輸出結果:
Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
4.4.8 倒序打印
1)說明:如果想倒序打印一組數據,可以用reverse。
2)案例實操:
需求:倒序打印10到1
for(i <- 1 to 10 reverse){
println(i)
}
4.5 While和do..While循環控制
While和do..While的使用和Java語言中用法相同。
4.5.1 While循環控制
1)基本語法
循環變量初始化
while (循環條件) {
循環體(語句)
循環變量迭代
}
說明:
(1)循環條件是返回一個布爾值的表達式
(2)while循環是先判斷再執行語句
(3)與for語句不同,while語句沒有返回值,即整個while語句的結果是Unit類型()
(4)因為while中沒有返回值,所以當要用該語句來計算並返回結果時,就不可避免的使用變量,而變量需要聲明在while循環的外部,那么就等同於循環的內部對外部的變量造成了影響,所以不推薦使用,而是推薦使用for循環。
2)案例實操
需求:輸出10句 "宋宋,喜歡海狗人參丸"
object TestWhile {
def main(args: Array[String]): Unit = {
var i = 0
while (i < 10) {
println("宋宋,喜歡海狗人參丸" + i)
i += 1
}
}
}
4.5.2 do..while循環控制
1)基本語法
循環變量初始化;
do{
循環體(語句)
循環變量迭代
} while(循環條件)
說明
(1)循環條件是返回一個布爾值的表達式
(2)do..while循環是先執行,再判斷
2)案例實操
需求:輸出10句 "宋宋,喜歡海狗人參丸"
object TestWhile {
def main(args: Array[String]): Unit = {
var i = 0
do {
println("宋宋,喜歡海狗人參丸" + i)
i += 1
} while (i < 10)
}
}
4.6 循環中斷
1)基本說明
Scala內置控制結構特地去掉了break和continue,是為了更好的適應函數式編程,推薦使用函數式的風格解決break和continue的功能,而不是一個關鍵字。Scala中使用breakable控制結構來實現break和continue功能。
2)案例實操
需求1:采用異常的方式退出循環
def main(args: Array[String]): Unit = {
try {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) throw new RuntimeException
}
}catch {
case e =>
}
println("正常結束循環")
}
需求2:采用Scala自帶的函數,退出循環
import scala.util.control.Breaks
def main(args: Array[String]): Unit = {
Breaks.breakable(
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) Breaks.break()
}
)
println("正常結束循環")
}
需求3:對break進行省略
import scala.util.control.Breaks._
object TestBreak {
def main(args: Array[String]): Unit = {
breakable {
for (elem <- 1 to 10) {
println(elem)
if (elem == 5) break
}
}
println("正常結束循環")
}
}
需求4:循環遍歷10以內的所有數據,奇數打印,偶數跳過(continue)
object TestBreak {
def main(args: Array[String]): Unit = {
for (elem <- 1 to 10) {
if (elem % 2 == 1) {
println(elem)
} else {
println("continue")
}
}
}
}
4.7 多重循環
1)基本說明
(1)將一個循環放在另一個循環體內,就形成了嵌套循環。其中,for,while,do…while均可以作為外層循環和內層循環。【建議一般使用兩層,最多不要超過3層】
(2)設外層循環次數為m次,內層為n次,則內層循環體實際上需要執行m*n次。
2)案例實操
需求:打印出九九乘法表
object TestWhile {
def main(args: Array[String]): Unit = {
for (i <- 1 to 9) {
for (j <- 1 to i) {
print(j + "*" + i + "=" + (i * j) + "\t")
}
println()
}
}
}
輸出結果:
第5章 函數式編程
1)面向對象編程
解決問題,分解對象,行為,屬性,然后通過對象的關系以及行為的調用來解決問題。
對象:用戶
行為:登錄、連接JDBC、讀取數據庫
屬性:用戶名、密碼
Scala語言是一個完全面向對象編程語言。萬物皆對象
對象的本質:對數據和行為的一個封裝
2)函數式編程
解決問題時,將問題分解成一個一個的步驟,將每個步驟進行封裝(函數),通過調用這些封裝好的步驟,解決問題。
例如:請求->用戶名、密碼->連接JDBC->讀取數據庫
Scala語言是一個完全函數式編程語言。萬物皆函數。
函數的本質:函數可以當做一個值進行傳遞
3)在Scala中函數式編程和面向對象編程完美融合在一起了。
5.1 函數基礎
5.1.1 函數基本語法
1)基本語法
2)案例實操
需求:定義一個函數,實現將傳入的名稱打印出來。
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)函數定義
def f(arg: String): Unit = {
println(arg)
}
// (2)函數調用
// 函數名(參數)
f("hello world")
}
}
5.1.2 函數和方法的區別
1)核心概念
(1)為完成某一功能的程序語句的集合,稱為函數。
(2)類中的函數稱之方法。
2)案例實操
(1)Scala語言可以在任何的語法結構中聲明任何的語法
(2)函數沒有重載和重寫的概念;方法可以進行重載和重寫
(3)Scala中函數可以嵌套定義
object TestFunction {
// (2)方法可以進行重載和重寫,程序可以執行
def main(): Unit = {
}
def main(args: Array[String]): Unit = {
// (1)Scala語言可以在任何的語法結構中聲明任何的語法
import java.util.Date
new Date()
// (2)函數沒有重載和重寫的概念,程序報錯
def test(): Unit ={
println("無參,無返回值")
}
test()
def test(name:String):Unit={
println()
}
//(3)Scala中函數可以嵌套定義
def test2(): Unit ={
def test3(name:String):Unit={
println("函數可以嵌套定義")
}
}
}
}
5.1.3 函數定義
1)函數定義
(1)函數1:無參,無返回值
(2)函數2:無參,有返回值
(3)函數3:有參,無返回值
(4)函數4:有參,有返回值
(5)函數5:多參,無返回值
(6)函數6:多參,有返回值
2)案例實操
package com.atguigu.chapter05
object TestFunctionDeclare {
def main(args: Array[String]): Unit = {
// 函數1:無參,無返回值
def test1(): Unit ={
println("無參,無返回值")
}
test1()
// 函數2:無參,有返回值
def test2():String={
return "無參,有返回值"
}
println(test2())
// 函數3:有參,無返回值
def test3(s:String):Unit={
println(s)
}
test3("jinlian")
// 函數4:有參,有返回值
def test4(s:String):String={
return s+"有參,有返回值"
}
println(test4("hello "))
// 函數5:多參,無返回值
def test5(name:String, age:Int):Unit={
println(s"$name, $age")
}
test5("dalang",40)
}
}
5.1.4 函數參數
1)案例實操
(1)可變參數
(2)如果參數列表中存在多個參數,那么可變參數一般放置在最后
(3)參數默認值,一般將有默認值的參數放置在參數列表的后面
(4)帶名參數
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)可變參數
def test( s : String* ): Unit = {
println(s)
}
// 有輸入參數:輸出 Array
test("Hello", "Scala")
// 無輸入參數:輸出List()
test()
// (2)如果參數列表中存在多個參數,那么可變參數一般放置在最后
def test2( name : String, s: String* ): Unit = {
println(name + "," + s)
}
test2("jinlian", "dalang")
// (3)參數默認值
def test3( name : String, age : Int = 30 ): Unit = {
println(s"$name, $age")
}
// 如果參數傳遞了值,那么會覆蓋默認值
test3("jinlian", 20)
// 如果參數有默認值,在調用的時候,可以省略這個參數
test3("dalang")
// 一般情況下,將有默認值的參數放置在參數列表的后面
def test4( sex : String = "男", name : String ): Unit = {
println(s"$name, $sex")
}
// Scala函數中參數傳遞是,從左到右
//test4("wusong")
//(4)帶名參數
test4(name="ximenqing")
}
}
5.1.5 函數至簡原則(重點)
函數至簡原則:能省則省
1)至簡原則細節
(1)return可以省略,Scala會使用函數體的最后一行代碼作為返回值
(2)如果函數體只有一行代碼,可以省略花括號
(3)返回值類型如果能夠推斷出來,那么可以省略(:和返回值類型一起省略)
(4)如果有return,則不能省略返回值類型,必須指定
(5)如果函數明確聲明unit,那么即使函數體中使用return關鍵字也不起作用
(6)Scala如果期望是無返回值類型,可以省略等號
(7)如果函數無參,但是聲明了參數列表,那么調用時,小括號,可加可不加
(8)如果函數沒有參數列表,那么小括號可以省略,調用時小括號必須省略
(9)如果不關心名稱,只關心邏輯處理,那么函數名(def)可以省略
2)案例實操
object TestFunction {
def main(args: Array[String]): Unit = {
// (0)函數標准寫法
def f( s : String ): String = {
return s + " jinlian"
}
println(f("Hello"))
// 至簡原則:能省則省
//(1) return可以省略,Scala會使用函數體的最后一行代碼作為返回值
def f1( s : String ): String = {
s + " jinlian"
}
println(f1("Hello"))
//(2)如果函數體只有一行代碼,可以省略花括號
def f2(s:String):String = s + " jinlian"
//(3)返回值類型如果能夠推斷出來,那么可以省略(:和返回值類型一起省略)
def f3( s : String ) = s + " jinlian"
println(f3("Hello3"))
//(4)如果有return,則不能省略返回值類型,必須指定。
def f4() :String = {
return "ximenqing4"
}
println(f4())
//(5)如果函數明確聲明unit,那么即使函數體中使用return關鍵字也不起作用
def f5(): Unit = {
return "dalang5"
}
println(f5())
//(6)Scala如果期望是無返回值類型,可以省略等號
// 將無返回值的函數稱之為過程
def f6() {
"dalang6"
}
println(f6())
//(7)如果函數無參,但是聲明了參數列表,那么調用時,小括號,可加可不加
def f7() = "dalang7"
println(f7())
println(f7)
//(8)如果函數沒有參數列表,那么小括號可以省略,調用時小括號必須省略
def f8 = "dalang"
//println(f8())
println(f8)
//(9)如果不關心名稱,只關心邏輯處理,那么函數名(def)可以省略
def f9 = (x:String)=>{println("wusong")}
def f10(f:String=>Unit) = {
f("")
}
f10(f9)
println(f10((x:String)=>{println("wusong")}))
}
}
5.2 函數高級
5.2.1 高階函數
在Scala中,函數是一等公民。怎么體現的呢?
對於一個函數我們可以:定義函數、調用函數
object TestFunction {
def main(args: Array[String]): Unit = {
// 調用函數
foo()
}
// 定義函數
def foo():Unit = {
println("foo...")
}
}
但是其實函數還有更高階的用法
1)函數可以作為值進行傳遞
object TestFunction {
def main(args: Array[String]): Unit = {
//(1)調用foo函數,把返回值給變量f
//val f = foo()
val f = foo
println(f)
//(2)在被調用函數foo后面加上 _,相當於把函數foo當成一個整體,傳遞給變量f1
val f1 = foo _
foo()
f1()
//(3)如果明確變量類型,那么不使用下划線也可以將函數作為整體傳遞給變量
var f2:()=>Int = foo
}
def foo():Int = {
println("foo...")
1
}
}
2)函數可以作為參數進行傳遞
def main(args: Array[String]): Unit = {
// (1)定義一個函數,函數參數還是一個函數簽名;f表示函數名稱;(Int,Int)表示輸入兩個Int參數;Int表示函數返回值
def f1(f: (Int, Int) => Int): Int = {
f(2, 4)
}
// (2)定義一個函數,參數和返回值類型和f1的輸入參數一致
def add(a: Int, b: Int): Int = a + b
// (3)將add函數作為參數傳遞給f1函數,如果能夠推斷出來不是調用,_可以省略
println(f1(add))
println(f1(add _))
//可以傳遞匿名函數
}
3)函數可以作為函數返回值返回
def main(args: Array[String]): Unit = {
def f1() = {
def f2() = {
}
f2 _
}
val f = f1()
// 因為f1函數的返回值依然為函數,所以可以變量f可以作為函數繼續調用
f()
// 上面的代碼可以簡化為
f1()()
}
5.2.2 匿名函數
1)說明
沒有名字的函數就是匿名函數。
(x:Int)=>{函數體}
x:表示輸入參數類型;Int:表示輸入參數類型;函數體:表示具體代碼邏輯
2)案例實操
需求1:傳遞的函數有一個參數
傳遞匿名函數至簡原則:
(1)參數的類型可以省略,會根據形參進行自動的推導
(2)類型省略之后,發現只有一個參數,則圓括號可以省略;其他情況:沒有參數和參數超過1的永遠不能省略圓括號。
(3)匿名函數如果只有一行,則大括號也可以省略
(4)如果參數只出現一次,則參數省略且后面參數可以用_代替
def main(args: Array[String]): Unit = {
// (1)定義一個函數:參數包含數據和邏輯函數
def operation(arr: Array[Int], op: Int => Int) = {
for (elem <- arr) yield op(elem)
}
// (2)定義邏輯函數
def op(ele: Int): Int = {
ele + 1
}
// (3)標准函數調用
val arr = operation(Array(1, 2, 3, 4), op)
println(arr.mkString(","))
// (4)采用匿名函數
val arr1 = operation(Array(1, 2, 3, 4), (ele: Int) => {
ele + 1
})
println(arr1.mkString(","))
// (4.1)參數的類型可以省略,會根據形參進行自動的推導;
val arr2 = operation(Array(1, 2, 3, 4), (ele) => {
ele + 1
})
println(arr2.mkString(","))
// (4.2)類型省略之后,發現只有一個參數,則圓括號可以省略;其他情況:沒有參數和參數超過1的永遠不能省略圓括號。
val arr3 = operation(Array(1, 2, 3, 4), ele => {
ele + 1
})
println(arr3.mkString(","))
// (4.3) 匿名函數如果只有一行,則大括號也可以省略
val arr4 = operation(Array(1, 2, 3, 4), ele => ele + 1)
println(arr4.mkString(","))
//(4.4)如果參數只出現一次,則參數省略且后面參數可以用_代替
val arr5 = operation(Array(1, 2, 3, 4), _ + 1)
println(arr5.mkString(","))
}
}
需求2:傳遞的函數有兩個參數
object TestFunction {
def main(args: Array[String]): Unit = {
def calculator(a: Int, b: Int, op: (Int, Int) => Int): Int = {
op(a, b)
}
// (1)標准版
println(calculator(2, 3, (x: Int, y: Int) => {x + y}))
// (2)如果只有一行,則大括號也可以省略
println(calculator(2, 3, (x: Int, y: Int) => x + y))
// (3)參數的類型可以省略,會根據形參進行自動的推導;
println(calculator(2, 3, (x , y) => x + y))
// (4)如果參數只出現一次,則參數省略且后面參數可以用_代替
println(calculator(2, 3, _ + _))
}
}
擴展練習
5.2.3 高階函數案例(集合再講)
需求:模擬Map映射、Filter過濾、Reduce聚合
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)map映射
def map(arr: Array[Int], op: Int => Int) = {
for (elem <- arr) yield op(elem)
}
val arr = map(Array(1, 2, 3, 4), (x: Int) => {
x * x
})
println(arr.mkString(","))
// (2)filter過濾。有參數,且參數再后面只使用一次,則參數省略且后面參數用_表示
def filter(arr:Array[Int],op:Int =>Boolean) ={
var arr1:ArrayBuffer[Int] = ArrayBuffer[Int]()
for(elem <- arr if op(elem)){
arr1.append(elem)
}
arr1.toArray
}
var arr1 = filter(Array(1, 2, 3, 4), _ % 2 == 1)
println(arr1.mkString(","))
// (3)reduce聚合。有多個參數,且每個參數再后面只使用一次,則參數省略且后面參數用_表示,第n個_代表第n個參數
def reduce(arr: Array[Int], op: (Int, Int) => Int) = {
var init: Int = arr(0)
for (elem <- 1 until arr.length) {
init = op(init, elem)
}
init
}
//val arr2 = reduce(Array(1, 2, 3, 4), (x, y) => x * y)
val arr2 = reduce(Array(1, 2, 3, 4), _ * _)
println(arr2)
}
}
5.2.4 函數柯里化&閉包
閉包:函數式編程的標配
1)說明
閉包:如果一個函數,訪問到了它的外部(局部)變量的值,那么這個函數和他所處的環境,稱為閉包
函數柯里化:把一個參數列表的多個參數,變成多個參數列表。
2)案例實操
(1)閉包
object TestFunction {
def main(args: Array[String]): Unit = {
def f1()={
var a:Int = 10
def f2(b:Int)={
a + b
}
f2 _
}
// 在調用時,f1函數執行完畢后,局部變量a應該隨着棧空間釋放掉
val f = f1()
// 但是在此處,變量a其實並沒有釋放,而是包含在了f2函數的內部,形成了閉合的效果
println(f(3))
println(f1()(3))
// 函數柯里化,其實就是將復雜的參數邏輯變得簡單化,函數柯里化一定存在閉包
def f3()(b:Int)={
a + b
}
println(f3()(3))
}
}
5.2.5 遞歸
1)說明
一個函數/方法在函數/方法體內又調用了本身,我們稱之為遞歸調用
2)案例實操
object TestFunction {
def main(args: Array[String]): Unit = {
// 階乘
// 遞歸算法
// 1) 方法調用自身
// 2) 方法必須要有跳出的邏輯
// 3) 方法調用自身時,傳遞的參數應該有規律
// 4) scala中的遞歸必須聲明函數返回值類型
println(test(5))
}
def test(i : Int) : Int = {
if (i == 1) {
1
} else {
i * test(I - 1)
}
}
}
5.2.6 控制抽象
1)值調用:把計算后的值傳遞過去
object TestControl {
def main(args: Array[String]): Unit = {
def f = ()=>{
println("f...")
10
}
foo(f())
}
def foo(a: Int):Unit = {
println(a)
println(a)
}
}
2)名調用:把代碼傳遞過去
object TestControl {
def main(args: Array[String]): Unit = {
def f = ()=>{
println("f...")
10
}
foo(f())
}
//def foo(a: Int):Unit = {
def foo(a: =>Int):Unit = {//注意這里變量a沒有小括號了
println(a)
println(a)
}
}
輸出結果:
f...
10
f...
10
注意:Java只有值調用;Scala既有值調用,又有名調用。
3)案例實操
object TestFunction {
def main(args: Array[String]): Unit = {
// (1)傳遞代碼塊
foo({
println("aaa")
})
// (2)小括號可以省略
foo{
println("aaa")
}
}
def foo(a: =>Unit):Unit = {
println(a)
println(a)
}
}
自定義一個While循環
object TestFunction {
def main(args: Array[String]): Unit = {
var i:Int = 1
myWhile(i <= 10){
println(i)
i +=1
}
}
def myWhile(condition: =>Boolean)(op: =>Unit):Unit={
if (condition){
op
myWhile(condition)(op)
}
}
}
5.2.7 惰性函數
1)說明
當函數返回值被聲明為lazy時,函數的執行將被推遲,直到我們首次對此取值,該函數才會執行。這種函數我們稱之為惰性函數。
2)案例實操
def main(args: Array[String]): Unit = {
lazy val res = sum(10, 30)
println("----------------")
println("res=" + res)
}
def sum(n1: Int, n2: Int): Int = {
println("sum被執行。。。")
return n1 + n2
}
輸出結果:
----------------
sum被執行。。。
res=40
注意:lazy不能修飾var類型的變量
第6章 面向對象
Scala的面向對象思想和Java的面向對象思想和概念是一致的。
Scala中語法和Java不同,補充了更多的功能。
6.1 Scala包
1)基本語法
package 包名.類名
2)Scala包的三大作用(和Java一樣)
(1)區分相同名字的類
(2)當類很多時,可以很好的管理類
(3)控制訪問范圍
6.1.1 包的命名
1)命名規則
只能包含數字、字母、下划線、小圓點.,但不能用數字開頭,也不要使用關鍵字。
2)案例實操
demo.class.exec1 //錯誤,因為 class 關鍵字
demo.12a //錯誤,數字開頭
3)命名規范
一般是小寫字母+小圓點
com.公司名.項目名.業務模塊名
4)案例實操
com.atguigu.oa.model
com.atguigu.oa.controller
com.sohu.bank.order
6.1.2 包說明(包語句)
1)說明
Scala有兩種包的管理風格,一種方式和Java的包管理風格相同,每個源文件一個包(包名和源文件所在路徑不要求必須一致),包名用“.”進行分隔以表示包的層級關系,如com.atguigu.scala。另一種風格,通過嵌套的風格表示層級關系,如下
package com{
package atguigu{
package scala{
}
}
}
第二種風格有以下特點:
(1)一個源文件中可以聲明多個package
(2)子包中的類可以直接訪問父包中的內容,而無需導包
2)案例實操
package com {
import com.atguigu.Inner //父包訪問子包需要導包
object Outer {
val out: String = "out"
def main(args: Array[String]): Unit = {
println(Inner.in)
}
}
package atguigu {
object Inner {
val in: String = "in"
def main(args: Array[String]): Unit = {
println(Outer.out) //子包訪問父包無需導包
}
}
}
}
package other {
}
6.1.3 包對象
在Scala中可以為每個包定義一個同名的包對象,定義在包對象中的成員,作為其對應包下所有class和object的共享變量,可以被直接訪問。
1)定義
package object com{
val shareValue="share"
def shareMethod()={}
}
1) 說明
(1)若使用Java的包管理風格,則包對象一般定義在其對應包下的package.scala文件中,包對象名與包名保持一致。
(2)如采用嵌套方式管理包,則包對象可與包定義在同一文件中,但是要保證包對象與包聲明在同一作用域中。
package com {
object Outer {
val out: String = "out"
def main(args: Array[String]): Unit = {
println(name)
}
}
}
package object com {
val name: String = "com"
}
6.1.4 導包說明
1)和Java一樣,可以在頂部使用import導入,在這個文件中的所有類都可以使用。
2)局部導入:什么時候使用,什么時候導入。在其作用范圍內都可以使用
3)通配符導入:import java.util._
4)給類起名:import java.util.{ArrayList=>JL}
5)屏蔽類:import java.util.{ArrayList =>_,_}
6)導入相同包的多個類:import java.util.{HashSet, ArrayList}
7)導入包的絕對路徑:new _root_.java.util.HashMap
package java {
package util {
class HashMap {
}
}
}
說明
import com.atguigu.Fruit |
引入com.atguigu包下Fruit(class和object) |
import com.atguigu._ |
引入com.atguigu下的所有成員 |
import com.atguigu.Fruit._ |
引入Fruit(object)的所有成員 |
import com.atguigu.{Fruit,Vegetable} |
引入com.atguigu下的Fruit和Vegetable |
import com.atguigu.{Fruit=>Shuiguo} |
引入com.atguigu包下的Fruit並更名為Shuiguo |
import com.atguigu.{Fruit=>Shuiguo,_} |
引入com.atguigu包下的所有成員,並將Fruit更名為Shuiguo |
import com.atguigu.{Fruit=>_,_} |
引入com.atguigu包下屏蔽Fruit類 |
new _root_.java.util.HashMap |
引入的Java的絕對路徑 |
2)注意
Scala中的三個默認導入分別是
import java.lang._
import scala._
import scala.Predef._
6.2 類和對象
類:可以看成一個模板
對象:表示具體的事物
6.2.1 定義類
1)回顧:Java中的類
如果類是public的,則必須和文件名一致。
一般,一個.java有一個public類
注意:Scala中沒有public,一個.scala中可以寫多個類。
1)基本語法
[修飾符] class 類名 {
類體
}
說明
(1)Scala語法中,類並不聲明為public,所有這些類都具有公有可見性(即默認就是public)
(2)一個Scala源文件可以包含多個類
2)案例實操
package com.atguigu.chapter06
//(1)Scala語法中,類並不聲明為public,所有這些類都具有公有可見性(即默認就是public)
class Person {
}
//(2)一個Scala源文件可以包含多個類
class Teacher{
}
6.2.2 屬性
屬性是類的一個組成部分
1)基本語法
[修飾符] var|val 屬性名稱 [:類型] = 屬性值
注:Bean屬性(@BeanPropetry),可以自動生成規范的setXxx/getXxx方法
2)案例實操
package com.atguigu.scala.test
import scala.beans.BeanProperty
class Person {
var name: String = "bobo" //定義屬性
var age: Int = _ // _表示給屬性一個默認值
//Bean屬性(@BeanProperty)
@BeanProperty var sex: String = "男"
//val修飾的屬性不能賦默認值,必須顯示指定
}
object Person {
def main(args: Array[String]): Unit = {
var person = new Person()
println(person.name)
person.setSex("女")
println(person.getSex)
}
}
6.3 封裝
封裝就是把抽象出的數據和對數據的操作封裝在一起,數據被保護在內部,程序的其它部分只有通過被授權的操作(成員方法),才能對數據進行操作。Java封裝操作如下,
(1)將屬性進行私有化
(2)提供一個公共的set方法,用於對屬性賦值
(3)提供一個公共的get方法,用於獲取屬性的值
Scala中的public屬性,底層實際為private,並通過get方法(obj.field())和set方法(obj.field_=(value))對其進行操作。所以Scala並不推薦將屬性設為private,再為其設置public的get和set方法的做法。但由於很多Java框架都利用反射調用getXXX和setXXX方法,有時候為了和這些框架兼容,也會為Scala的屬性設置getXXX和setXXX方法(通過@BeanProperty注解實現)。
6.1.5 訪問權限
1)說明
在Java中,訪問權限分為:public,private,protected和默認。在Scala中,你可以通過類似的修飾符達到同樣的效果。但是使用上有區別。
(1)Scala 中屬性和方法的默認訪問權限為public,但Scala中無public關鍵字。
(2)private為私有權限,只在類的內部和伴生對象中可用。
(3)protected為受保護權限,Scala中受保護權限比Java中更嚴格,同類、子類可以訪問,同包無法訪問。
(4)private[包名]增加包訪問權限,包名下的其他類也可以使用
2)案例實操
package com.atguigu.scala.test
class Person {
private var name: String = "bobo"
protected var age: Int = 18
private[test] var sex: String = "男"
def say(): Unit = {
println(name)
}
}
object Person {
def main(args: Array[String]): Unit = {
val person = new Person
person.say()
println(person.name)
println(person.age)
}
}
class Teacher extends Person {
def test(): Unit = {
this.age
this.sex
}
}
class Animal {
def test: Unit = {
new Person().sex
}
}
6.2.3 方法
1)基本語法
def 方法名(參數列表) [:返回值類型] = {
方法體
}
2)案例實操
class Person {
def sum(n1:Int, n2:Int) : Int = {
n1 + n2
}
}
object Person {
def main(args: Array[String]): Unit = {
val person = new Person()
println(person.sum(10, 20))
}
}
6.2.4 創建對象
1)基本語法
val | var 對象名 [:類型] = new 類型()
2)案例實操
(1)val修飾對象,不能改變對象的引用(即:內存地址),可以改變對象屬性的值。
(2)var修飾對象,可以修改對象的引用和修改對象的屬性值
(3)自動推導變量類型不能多態,所以多態需要顯示聲明
class Person {
var name: String = "canglaoshi"
}
object Person {
def main(args: Array[String]): Unit = {
//val修飾對象,不能改變對象的引用(即:內存地址),可以改變對象屬性的值。
val person = new Person()
person.name = "bobo"
// person = new Person()// 錯誤的
println(person.name)
}
}
6.2.5 構造器
和Java一樣,Scala構造對象也需要調用構造方法,並且可以有任意多個構造方法。
Scala類的構造器包括:主構造器和輔助構造器
1)基本語法
class 類名(形參列表) { // 主構造器
// 類體
def this(形參列表) { // 輔助構造器
}
def this(形參列表) { //輔助構造器可以有多個...
}
}
說明:
(1)輔助構造器,函數的名稱this,可以有多個,編譯器通過參數的個數及類型來區分。
(2)輔助構造方法不能直接構建對象,必須直接或者間接調用主構造方法。
(3)構造器調用其他另外的構造器,要求被調用構造器必須提前聲明。
2)案例實操
(1)如果主構造器無參數,小括號可省略,構建對象時調用的構造方法的小括號也可以省略。
//(1)如果主構造器無參數,小括號可省略
//class Person (){
class Person {
var name: String = _
var age: Int = _
def this(age: Int) {
this()
this.age = age
println("輔助構造器")
}
def this(age: Int, name: String) {
this(age)
this.name = name
}
println("主構造器")
}
object Person {
def main(args: Array[String]): Unit = {
val person2 = new Person(18)
}
}
6.2.6 構造器參數
1)說明
Scala類的主構造器函數的形參包括三種類型:未用任何修飾、var修飾、val修飾
(1)未用任何修飾符修飾,這個參數就是一個局部變量
(2)var修飾參數,作為類的成員屬性使用,可以修改
(3)val修飾參數,作為類只讀屬性使用,不能修改
2)案例實操
class Person(name: String, var age: Int, val sex: String) {
}
object Test {
def main(args: Array[String]): Unit = {
var person = new Person("bobo", 18, "男")
// (1)未用任何修飾符修飾,這個參數就是一個局部變量
// printf(person.name)
// (2)var修飾參數,作為類的成員屬性使用,可以修改
person.age = 19
println(person.age)
// (3)val修飾參數,作為類的只讀屬性使用,不能修改
// person.sex = "女"
println(person.sex)
}
}
6.4 繼承
1)基本語法
class 子類名 extends 父類名 { 類體 }
(1)子類繼承父類的屬性和方法
(2)scala是單繼承
2)案例實操
(1)子類繼承父類的屬性和方法
(2)繼承的調用順序:父類構造器->子類構造器
class Person(nameParam: String) {
var name = nameParam
var age: Int = _
def this(nameParam: String, ageParam: Int) {
this(nameParam)
this.age = ageParam
println("父類輔助構造器")
}
println("父類主構造器")
}
class Emp(nameParam: String, ageParam: Int) extends Person(nameParam, ageParam) {
var empNo: Int = _
def this(nameParam: String, ageParam: Int, empNoParam: Int) {
this(nameParam, ageParam)
this.empNo = empNoParam
println("子類的輔助構造器")
}
println("子類主構造器")
}
object Test {
def main(args: Array[String]): Unit = {
new Emp("z3", 11,1001)
}
}
6.5 抽象屬性和抽象方法
6.5.1 抽象屬性和抽象方法
1)基本語法
(1)定義抽象類:abstract class Person{} //通過abstract關鍵字標記抽象類
(2)定義抽象屬性:val|var name:String //一個屬性沒有初始化,就是抽象屬性
(3)定義抽象方法:def hello():String //只聲明而沒有實現的方法,就是抽象方法
案例實操
abstract class Person {
val name: String
def hello(): Unit
}
class Teacher extends Person {
val name: String = "teacher"
def hello(): Unit = {
println("hello teacher")
}
}
2)繼承&重寫
(1)如果父類為抽象類,那么子類需要將抽象的屬性和方法實現,否則子類也需聲明為抽象類
(2)重寫非抽象方法需要用override修飾,重寫抽象方法則可以不加override。
(3)子類中調用父類的方法使用super關鍵字
(4)子類對抽象屬性進行實現,父類抽象屬性可以用var修飾;
子類對非抽象屬性重寫,父類非抽象屬性只支持val類型,而不支持var。
因為var修飾的為可變變量,子類繼承之后就可以直接使用,沒有必要重寫
(5)Scala中屬性和方法都是動態綁定,而Java中只有方法為動態綁定。
案例實操(對比Java與Scala的重寫)
Scala
class Person {
val name: String = "person"
def hello(): Unit = {
println("hello person")
}
}
class Teacher extends Person {
override val name: String = "teacher"
override def hello(): Unit = {
println("hello teacher")
}
}
object Test {
def main(args: Array[String]): Unit = {
val teacher: Teacher = new Teacher()
println(teacher.name)
teacher.hello()
val teacher1:Person = new Teacher
println(teacher1.name)
teacher1.hello()
}
}
Java
class Person {
public String name = "person";
public void hello() {
System.out.println("hello person");
}
}
class Teacher extends Person {
public String name = "teacher";
@Override
public void hello() {
System.out.println("hello teacher");
}
}
public class TestDynamic {
public static void main(String[] args) {
Teacher teacher = new Teacher();
Person teacher1 = new Teacher();
System.out.println(teacher.name);
teacher.hello();
System.out.println(teacher1.name);
teacher1.hello();
}
}
結果對比
Scala Java
6.5.2 匿名子類
1)說明
Java一樣,可以通過包含帶有定義或重寫的代碼塊的方式創建一個匿名的子類。
2)案例實操
abstract class Person {
val name: String
def hello(): Unit
}
object Test {
def main(args: Array[String]): Unit = {
val person = new Person {
override val name: String = "teacher"
override def hello(): Unit = println("hello teacher")
}
}
}
6.6 單例對象(伴生對象)
Scala語言是完全面向對象的語言,所以並沒有靜態的操作(即在Scala中沒有靜態的概念)。但是為了能夠和Java語言交互(因為Java中有靜態概念),就產生了一種特殊的對象來模擬類對象,該對象為單例對象。若單例對象名與類名一致,則稱該單例對象這個類的伴生對象,這個類的所有“靜態”內容都可以放置在它的伴生對象中聲明。
6.6.1 單例對象語法
1)基本語法
object Person{
val country:String="China"
}
2)說明
(1)單例對象采用object關鍵字聲明
(2)單例對象對應的類稱之為伴生類,伴生對象的名稱應該和伴生類名一致。
(3)單例對象中的屬性和方法都可以通過伴生對象名(類名)直接調用訪問。
3)案例實操
//(1)伴生對象采用object關鍵字聲明
object Person {
var country: String = "China"
}
//(2)伴生對象對應的類稱之為伴生類,伴生對象的名稱應該和伴生類名一致。
class Person {
var name: String = "bobo"
}
object Test {
def main(args: Array[String]): Unit = {
//(3)伴生對象中的屬性和方法都可以通過伴生對象名(類名)直接調用訪問。
println(Person.country)
}
}
6.6.2 apply方法
1)說明
(1)通過伴生對象的apply方法,實現不使用new方法創建對象。
(2)如果想讓主構造器變成私有的,可以在()之前加上private。
(3)apply方法可以重載。
(4)Scala中obj(arg)的語句實際是在調用該對象的apply方法,即obj.apply(arg)。用以統一面向對象編程和函數式編程的風格。
(5)當使用new關鍵字構建對象時,調用的其實是類的構造方法,當直接使用類名構建對象時,調用的其實時伴生對象的apply方法。
2)案例實操
object Test {
def main(args: Array[String]): Unit = {
//(1)通過伴生對象的apply方法,實現不使用new關鍵字創建對象。
val p1 = Person()
println("p1.name=" + p1.name)
val p2 = Person("bobo")
println("p2.name=" + p2.name)
}
}
//(2)如果想讓主構造器變成私有的,可以在()之前加上private
class Person private(cName: String) {
var name: String = cName
}
object Person {
def apply(): Person = {
println("apply空參被調用")
new Person("xx")
}
def apply(name: String): Person = {
println("apply有參被調用")
new Person(name)
}
//注意:也可以創建其它類型對象,並不一定是伴生類對象
}
擴展:在Scala中實現單例模式
6.7 特質(Trait)
Scala語言中,采用特質trait(特征)來代替接口的概念,也就是說,多個類具有相同的特質(特征)時,就可以將這個特質(特征)獨立出來,采用關鍵字trait聲明。
Scala中的trait中即可以有抽象屬性和方法,也可以有具體的屬性和方法,一個類可以混入(mixin)多個特質。這種感覺類似於Java中的抽象類。
Scala引入trait特征,第一可以替代Java的接口,第二個也是對單繼承機制的一種補充。
6.7.1 特質聲明
1)基本語法
trait 特質名 {
trait主體
}
2)案例實操
trait PersonTrait {
// 聲明屬性
var name:String = _
// 聲明方法
def eat():Unit={
}
// 抽象屬性
var age:Int
// 抽象方法
def say():Unit
}
通過查看字節碼,可以看到特質=抽象類+接口
6.7.2 特質基本語法
一個類具有某種特質(特征),就意味着這個類滿足了這個特質(特征)的所有要素,所以在使用時,也采用了extends關鍵字,如果有多個特質或存在父類,那么需要采用with關鍵字連接。
1)基本語法:
沒有父類:class 類名 extends 特質1 with 特質2 with 特質3 …
有父類:class 類名 extends 父類 with 特質1 with 特質2 with 特質3…
2)說明
(1)類和特質的關系:使用繼承的關系。
(2)當一個類去繼承特質時,第一個連接詞是extends,后面是with。
(3)如果一個類在同時繼承特質和父類時,應當把父類寫在extends后。
3)案例實操
(1)特質可以同時擁有抽象方法和具體方法
(2)一個類可以混入(mixin)多個特質
(3)所有的Java接口都可以當做Scala特質使用
(4)動態混入:可靈活的擴展類的功能
(4.1)動態混入:創建對象時混入trait,而無需使類混入該trait
(4.2)如果混入的trait中有未實現的方法,則需要實現
trait PersonTrait {
//(1)特質可以同時擁有抽象方法和具體方法
// 聲明屬性
var name: String = _
// 抽象屬性
var age: Int
// 聲明方法
def eat(): Unit = {
println("eat")
}
// 抽象方法
def say(): Unit
}
trait SexTrait {
var sex: String
}
//(2)一個類可以實現/繼承多個特質
//(3)所有的Java接口都可以當做Scala特質使用
class Teacher extends PersonTrait with java.io.Serializable {
override def say(): Unit = {
println("say")
}
override var age: Int = _
}
object TestTrait {
def main(args: Array[String]): Unit = {
val teacher = new Teacher
teacher.say()
teacher.eat()
//(4)動態混入:可靈活的擴展類的功能
val t2 = new Teacher with SexTrait {
override var sex: String = "男"
}
//調用混入trait的屬性
println(t2.sex)
}
}
6.7.3 特質疊加
由於一個類可以混入(mixin)多個trait,且trait中可以有具體的屬性和方法,若混入的特質中具有相同的方法(方法名,參數列表,返回值均相同),必然會出現繼承沖突問題。沖突分為以下兩種:
第一種,一個類(Sub)混入的兩個trait(TraitA,TraitB)中具有相同的具體方法,且兩個trait之間沒有任何關系,解決這類沖突問題,直接在類(Sub)中重寫沖突方法。
第二種,一個類(Sub)混入的兩個trait(TraitA,TraitB)中具有相同的具體方法,且兩個trait繼承自相同的trait(TraitC),及所謂的“鑽石問題”,解決這類沖突問題,Scala采用了特質疊加的策略。
所謂的特質疊加,就是將混入的多個trait中的沖突方法疊加起來,案例如下,
trait Ball {
def describe(): String = {
"ball"
}
}
trait Color extends Ball {
override def describe(): String = {
"blue-" + super.describe()
}
}
trait Category extends Ball {
override def describe(): String = {
"foot-" + super.describe()
}
}
class MyBall extends Category with Color {
override def describe(): String = {
"my ball is a " + super.describe()
}
}
object TestTrait {
def main(args: Array[String]): Unit = {
println(new MyBall().describe())
}
}
結果如下:
6.7.4 特質疊加執行順序
思考:上述案例中的super.describe()調用的是父trait中的方法嗎?
當一個類混入多個特質的時候,scala會對所有的特質及其父特質按照一定的順序進行排序,而此案例中的super.describe()調用的實際上是排好序后的下一個特質中的describe()方法。,排序規則如下:
結論:
(1)案例中的super,不是表示其父特質對象,而是表示上述疊加順序中的下一個特質,即,MyClass中的super指代Color,Color中的super指代Category,Category中的super指代Ball。
(2)如果想要調用某個指定的混入特質中的方法,可以增加約束:super[],例如super[Category].describe()。
6.7.5 特質自身類型
1)說明
自身類型可實現依賴注入的功能。
2)案例實操
class User(val name: String, val age: Int)
trait Dao {
def insert(user: User) = {
println("insert into database :" + user.name)
}
}
trait APP {
_: Dao =>
def login(user: User): Unit = {
println("login :" + user.name)
insert(user)
}
}
object MyApp extends APP with Dao {
def main(args: Array[String]): Unit = {
login(new User("bobo", 11))
}
}
6.7.6特質和抽象類的區別
1.優先使用特質。一個類擴展多個特質是很方便的,但卻只能擴展一個抽象類。
2.如果你需要構造函數參數,使用抽象類。因為抽象類可以定義帶參數的構造函數,而特質不行(有無參構造)。
6.8 擴展
6.8.1 類型檢查和轉換
1)說明
(1)obj.isInstanceOf[T]:判斷obj是不是T類型。
(2)obj.asInstanceOf[T]:將obj強轉成T類型。
(3)classOf獲取對象的類名。
2)案例實操
class Person{
}
object Person {
def main(args: Array[String]): Unit = {
val person = new Person
//(1)判斷對象是否為某個類型的實例
val bool: Boolean = person.isInstanceOf[Person]
if ( bool ) {
//(2)將對象轉換為某個類型的實例
val p1: Person = person.asInstanceOf[Person]
println(p1)
}
//(3)獲取類的信息
val pClass: Class[Person] = classOf[Person]
println(pClass)
}
}
6.8.2 枚舉類和應用類
1)說明
枚舉類:需要繼承Enumeration
應用類:需要繼承App
2)案例實操
object Test {
def main(args: Array[String]): Unit = {
println(Color.RED)
}
}
// 枚舉類
object Color extends Enumeration {
val RED = Value(1, "red")
val YELLOW = Value(2, "yellow")
val BLUE = Value(3, "blue")
}
// 應用類
object Test20 extends App {
println("xxxxxxxxxxx");
}
6.8.3 Type定義新類型
1)說明
使用type關鍵字可以定義新的數據數據類型名稱,本質上就是類型的一個別名
2)案例實操
object Test {
def main(args: Array[String]): Unit = {
type S=String
var v:S="abc"
def test():S="xyz"
}
}
第7章 集合
7.1 集合簡介
1)Scala的集合有三大類:序列Seq、集Set、映射Map,所有的集合都擴展自Iterable特質。
2)對於幾乎所有的集合類,Scala都同時提供了可變和不可變的版本,分別位於以下兩個包
不可變集合:scala.collection.immutable
可變集合: scala.collection.mutable
3)Scala不可變集合,就是指該集合對象不可修改,每次修改就會返回一個新對象,而不會對原對象進行修改。類似於java中的String對象
4)可變集合,就是這個集合可以直接對原對象進行修改,而不會返回新的對象。類似於java中StringBuilder對象
建議:在操作集合的時候,不可變用符號,可變用方法
7.1.1 不可變集合繼承圖
1)Set、Map是Java中也有的集合
2)Seq是Java沒有的,我們發現List歸屬到Seq了,因此這里的List就和Java不是同一個概念了
3)我們前面的for循環有一個 1 to 3,就是IndexedSeq下的Range
4)String也是屬於IndexedSeq
5)我們發現經典的數據結構比如Queue和Stack被歸屬到LinearSeq(線性序列)
6)大家注意Scala中的Map體系有一個SortedMap,說明Scala的Map可以支持排序
7)IndexedSeq和LinearSeq的區別:
(1)IndexedSeq是通過索引來查找和定位,因此速度快,比如String就是一個索引集合,通過索引即可定位
(2)LinearSeq是線型的,即有頭尾的概念,這種數據結構一般是通過遍歷來查找
7.1.2 可變集合繼承圖
7.2 數組
7.2.1 不可變數組
1)第一種方式定義數組
定義:val arr1 = new Array[Int](10)
(1)new是關鍵字
(2)[Int]是指定可以存放的數據類型,如果希望存放任意數據類型,則指定Any
(3)(10),表示數組的大小,確定后就不可以變化
2)案例實操
object TestArray{
def main(args: Array[String]): Unit = {
//(1)數組定義
val arr01 = new Array[Int](4)
println(arr01.length) // 4
//(2)數組賦值
//(2.1)修改某個元素的值
arr01(3) = 10
//(2.2)采用方法的形式給數組賦值
arr01.update(0,1)
//(3)遍歷數組
//(3.1)查看數組
println(arr01.mkString(","))
//(3.2)普通遍歷
for (i <- arr01) {
println(i)
}
//(3.3)簡化遍歷
def printx(elem:Int): Unit = {
println(elem)
}
arr01.foreach(printx)
// arr01.foreach((x)=>{println(x)})
// arr01.foreach(println(_))
arr01.foreach(println)
//(4)增加元素(由於創建的是不可變數組,增加元素,其實是產生新的數組)
println(arr01)
val ints: Array[Int] = arr01 :+ 5
println(ints)
}
}
3)第二種方式定義數組
val arr1 = Array(1, 2)
(1)在定義數組時,直接賦初始值
(2)使用apply方法創建數組對象
4)案例實操
object TestArray{
def main(args: Array[String]): Unit = {
var arr02 = Array(1, 3, "bobo")
println(arr02.length)
for (i <- arr02) {
println(i)
}
}
}
7.2.2 可變數組
1)定義變長數組
val arr01 = ArrayBuffer[Any](3, 2, 5)
(1)[Any]存放任意數據類型
(2)(3, 2, 5)初始化好的三個元素
(3)ArrayBuffer需要引入scala.collection.mutable.ArrayBuffer
2)案例實操
(1)ArrayBuffer是有序的集合
(2)增加元素使用的是append方法(),支持可變參數
import scala.collection.mutable.ArrayBuffer
object TestArrayBuffer {
def main(args: Array[String]): Unit = {
//(1)創建並初始賦值可變數組
val arr01 = ArrayBuffer[Any](1, 2, 3)
//(2)遍歷數組
for (i <- arr01) {
println(i)
}
println(arr01.length) // 3
println("arr01.hash=" + arr01.hashCode())
//(3)增加元素
//(3.1)追加數據
arr01.+=(4)
//(3.2)向數組最后追加數據
arr01.append(5,6)
//(3.3)向指定的位置插入數據
arr01.insert(0,7,8)
println("arr01.hash=" + arr01.hashCode())
//(4)修改元素
arr01(1) = 9 //修改第2個元素的值
println("--------------------------")
for (i <- arr01) {
println(i)
}
println(arr01.length) // 5
}
}
7.2.3 不可變數組與可變數組的轉換
1)說明
arr1.toBuffer //不可變數組轉可變數組
arr2.toArray //可變數組轉不可變數組
(1)arr2.toArray返回結果才是一個不可變數組,arr2本身沒有變化
(2)arr1.toBuffer返回結果才是一個可變數組,arr1本身沒有變化
2)案例實操
object TestArrayBuffer {
def main(args: Array[String]): Unit = {
//(1)創建一個空的可變數組
val arr2 = ArrayBuffer[Int]()
//(2)追加值
arr2.append(1, 2, 3)
println(arr2) // 1,2,3
//(3)ArrayBuffer ==> Array
//(3.1)arr2.toArray 返回的結果是一個新的定長數組集合
//(3.2)arr2它沒有變化
val newArr = arr2.toArray
println(newArr)
//(4)Array ===> ArrayBuffer
//(4.1)newArr.toBuffer 返回一個變長數組 newArr2
//(4.2)newArr 沒有任何變化,依然是定長數組
val newArr2 = newArr.toBuffer
newArr2.append(123)
println(newArr2)
}
}
7.2.4 多維數組
1)多維數組定義
val arr = Array.ofDim[Double](3,4)
說明:二維數組中有三個一維數組,每個一維數組中有四個元素
2)案例實操
object DimArray {
def main(args: Array[String]): Unit = {
//(1)創建了一個二維數組, 有三個元素,每個元素是,含有4個元素一維數組()
val arr = Array.ofDim[Int](3, 4)
arr(1)(2) = 88
//(2)遍歷二維數組
for (i <- arr) { //i 就是一維數組
for (j <- i) {
print(j + " ")
}
println()
}
}
}
7.3 Seq集合(List)
7.3.1 不可變List
1)說明
(1)List默認為不可變集合
(2)創建一個List(數據有順序,可重復)
(3)遍歷List
(4)List增加數據
(5)集合間合並:將一個整體拆成一個一個的個體,稱為扁平化
(6)取指定數據
(7)空集合Nil
2)案例實操
object TestList {
def main(args: Array[String]): Unit = {
//(1)List默認為不可變集合
//(2)創建一個List(數據有順序,可重復)
val list: List[Int] = List(1,2,3,4,3)
//(7)空集合Nil
val list5 = 1::2::3::4::Nil
//(4)List增加數據
//(4.1)::的運算規則從右向左
//val list1 = 5::list
val list1 = 7::6::5::list
//(4.2)添加到第一個元素位置
val list2 = list.+:(5)
//(5)集合間合並:將一個整體拆成一個一個的個體,稱為扁平化
val list3 = List(8,9)
//val list4 = list3::list1
val list4 = list3:::list1
//(6)取指定數據
println(list(0))
//(3)遍歷List
//list.foreach(println)
//list1.foreach(println)
//list3.foreach(println)
//list4.foreach(println)
list5.foreach(println)
}
}
7.3.2 可變ListBuffer
1)說明
(1)創建一個可變集合ListBuffer
(2)向集合中添加數據
(3)打印集合數據
2)案例實操
import scala.collection.mutable.ListBuffer
object TestList {
def main(args: Array[String]): Unit = {
//(1)創建一個可變集合
val buffer = ListBuffer(1,2,3,4)
//(2)向集合中添加數據
buffer.+=(5)
buffer.append(6)
buffer.insert(1,2)
//(3)打印集合數據
buffer.foreach(println)
//(4)修改數據
buffer(1) = 6
buffer.update(1,7)
//(5)刪除數據
buffer.-(5)
buffer.-=(5)
buffer.remove(5)
}
}
7.4 Set集合
默認情況下,Scala使用的是不可變集合,如果你想使用可變集合,需要引用 scala.collection.mutable.Set 包
7.4.1 不可變Set
1)說明
(1)Set默認是不可變集合,數據無序
(2)數據不可重復
(3)遍歷集合
2)案例實操
object TestSet {
def main(args: Array[String]): Unit = {
//(1)Set默認是不可變集合,數據無序
val set = Set(1,2,3,4,5,6)
//(2)數據不可重復
val set1 = Set(1,2,3,4,5,6,3)
//(3)遍歷集合
for(x<-set1){
println(x)
}
}
}
7.4.2 可變mutable.Set
1)說明
(1)創建可變集合mutable.Set
(2)打印集合
(3)集合添加元素
(4)向集合中添加元素,返回一個新的Set
(5)刪除數據
2)案例實操
object TestSet {
def main(args: Array[String]): Unit = {
//(1)創建可變集合
val set = mutable.Set(1,2,3,4,5,6)
//(3)集合添加元素
set += 8
//(4)向集合中添加元素,返回一個新的Set
val ints = set.+(9)
println(ints)
println("set2=" + set)
//(5)刪除數據
set-=(5)
//(2)打印集合
set.foreach(println)
println(set.mkString(","))
}
}
7.5 Map集合
Scala中的Map和Java類似,也是一個散列表,它存儲的內容也是鍵值對(key-value)映射
7.5.1 不可變Map
1)說明
(1)創建不可變集合Map
(2)循環打印
(3)訪問數據
(4)如果key不存在,返回0
2)案例實操
object TestMap {
def main(args: Array[String]): Unit = {
// Map
//(1)創建不可變集合Map
val map = Map( "a"->1, "b"->2, "c"->3 )
//(3)訪問數據
for (elem <- map.keys) {
// 使用get訪問map集合的數據,會返回特殊類型Option(選項):有值(Some),無值(None)
println(elem + "=" + map.get(elem).get)
}
//(4)如果key不存在,返回0
println(map.get("d").getOrElse(0))
println(map.getOrElse("d", 0))
//(2)循環打印
map.foreach((kv)=>{println(kv)})
}
}
7.5.2 可變Map
1)說明
(1)創建可變集合
(2)打印集合
(3)向集合增加數據
(4)刪除數據
(5)修改數據
2)案例實操
object TestSet {
def main(args: Array[String]): Unit = {
//(1)創建可變集合
val map = mutable.Map( "a"->1, "b"->2, "c"->3 )
//(3)向集合增加數據
map.+=("d"->4)
// 將數值4添加到集合,並把集合中原值1返回
val maybeInt: Option[Int] = map.put("a", 4)
println(maybeInt.getOrElse(0))
//(4)刪除數據
map.-=("b", "c")
//(5)修改數據
map.update("d",5)
map("d") = 5
//(2)打印集合
map.foreach((kv)=>{println(kv)})
}
}
7.6 元組
1)說明
元組也是可以理解為一個容器,可以存放各種相同或不同類型的數據。說的簡單點,就是將多個無關的數據封裝為一個整體,稱為元組。
注意:元組中最大只能有22個元素。
2)案例實操
(1)聲明元組的方式:(元素1,元素2,元素3)
(2)訪問元組
(3)Map中的鍵值對其實就是元組,只不過元組的元素個數為2,稱之為對偶
object TestTuple {
def main(args: Array[String]): Unit = {
//(1)聲明元組的方式:(元素1,元素2,元素3)
val tuple: (Int, String, Boolean) = (40,"bobo",true)
//(2)訪問元組
//(2.1)通過元素的順序進行訪問,調用方式:_順序號
println(tuple._1)
println(tuple._2)
println(tuple._3)
//(2.2)通過索引訪問數據
println(tuple.productElement(0))
//(2.3)通過迭代器訪問數據
for (elem <- tuple.productIterator) {
println(elem)
}
//(3)Map中的鍵值對其實就是元組,只不過元組的元素個數為2,稱之為對偶
val map = Map("a"->1, "b"->2, "c"->3)
val map1 = Map(("a",1), ("b",2), ("c",3))
map.foreach(tuple=>{println(tuple._1 + "=" + tuple._2)})
}
}
7.7 集合常用函數
7.7.1 基本屬性和常用操作
1)說明
(1)獲取集合長度
(2)獲取集合大小
(3)循環遍歷
(4)迭代器
(5)生成字符串
(6)是否包含
2)案例實操
object TestList {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
//(1)獲取集合長度
println(list.length)
//(2)獲取集合大小,等同於length
println(list.size)
//(3)循環遍歷
list.foreach(println)
//(4)迭代器
for (elem <- list.itera tor) {
println(elem)
}
//(5)生成字符串
println(list.mkString(","))
//(6)是否包含
println(list.contains(3))
}
}
7.7.2 衍生集合
1)說明
(1)獲取集合的頭
(2)獲取集合的尾(不是頭的就是尾)
(3)集合最后一個數據
(4)集合初始數據(不包含最后一個)
(5)反轉
(6)取前(后)n個元素
(7)去掉前(后)n個元素
(8)並集
(9)交集
(10)差集
(11)拉鏈
(12)滑窗
2)案例實操
object TestList {
def main(args: Array[String]): Unit = {
val list1: List[Int] = List(1, 2, 3, 4, 5, 6, 7)
val list2: List[Int] = List(4, 5, 6, 7, 8, 9, 10)
//(1)獲取集合的頭
println(list1.head)
//(2)獲取集合的尾(不是頭的就是尾)
println(list1.tail)
//(3)集合最后一個數據
println(list1.last)
//(4)集合初始數據(不包含最后一個)
println(list1.init)
//(5)反轉
println(list1.reverse)
//(6)取前(后)n個元素
println(list1.take(3))
println(list1.takeRight(3))
//(7)去掉前(后)n個元素
println(list1.drop(3))
println(list1.dropRight(3))
//(8)並集
println(list1.union(list2))
//(9)交集
println(list1.intersect(list2))
//(10)差集
println(list1.diff(list2))
//(11)拉鏈 注:如果兩個集合的元素個數不相等,那么會將同等數量的數據進行拉鏈,多余的數據省略不用
println(list1.zip(list2))
//(12)滑窗
list1.sliding(2, 5).foreach(println)
}
}
7.7.3 集合計算初級函數
1)說明
(1)求和
(2)求乘積
(3)最大值
(4)最小值
(5)排序
2)實操
object TestList {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 5, -3, 4, 2, -7, 6)
//(1)求和
println(list.sum)
//(2)求乘積
println(list.product)
//(3)最大值
println(list.max)
//(4)最小值
println(list.min)
//(5)排序
// (5.1)按照元素大小排序
println(list.sortBy(x => x))
// (5.2)按照元素的絕對值大小排序
println(list.sortBy(x => x.abs))
// (5.3)按元素大小升序排序
println(list.sortWith((x, y) => x < y))
// (5.4)按元素大小降序排序
println(list.sortWith((x, y) => x > y))
}
}
(1)sorted
對一個集合進行自然排序,通過傳遞隱式的Ordering
(2)sortBy
對一個屬性或多個屬性進行排序,通過它的類型。
(3)sortWith
基於函數的排序,通過一個comparator函數,實現自定義排序的邏輯。
7.7.4 集合計算高級函數
1)說明
(1)過濾
遍歷一個集合並從中獲取滿足指定條件的元素組成一個新的集合
(2)轉化/映射(map)
將集合中的每一個元素映射到某一個函數
(3)扁平化
(4)扁平化+映射 注:flatMap相當於先進行map操作,在進行flatten操作
集合中的每個元素的子元素映射到某個函數並返回新集合
(5)分組(group)
按照指定的規則對集合的元素進行分組
(6)簡化(歸約)
(7)折疊
2)實操
object TestList {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val nestedList: List[List[Int]] = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
val wordList: List[String] = List("hello world", "hello atguigu", "hello scala")
//(1)過濾
println(list.filter(x => x % 2 == 0))
//(2)轉化/映射
println(list.map(x => x + 1))
//(3)扁平化
println(nestedList.flatten)
//(4)扁平化+映射 注:flatMap相當於先進行map操作,在進行flatten操作
println(wordList.flatMap(x => x.split(" ")))
//(5)分組
println(list.groupBy(x => x % 2))
}
}
3)Reduce方法
Reduce簡化(歸約) :通過指定的邏輯將集合中的數據進行聚合,從而減少數據,最終獲取結果。
案例實操
object TestReduce {
def main(args: Array[String]): Unit = {
val list = List(1,2,3,4)
// 將數據兩兩結合,實現運算規則
val i: Int = list.reduce( (x,y) => x-y )
println("i = " + i)
// 從源碼的角度,reduce底層調用的其實就是reduceLeft
//val i1 = list.reduceLeft((x,y) => x-y)
// ((4-3)-2-1) = -2
val i2 = list.reduceRight((x,y) => x-y)
println(i2)
}
}
4)Fold方法
Fold折疊:化簡的一種特殊情況。
(1)案例實操:fold基本使用
object TestFold {
def main(args: Array[String]): Unit = {
val list = List(1,2,3,4)
// fold方法使用了函數柯里化,存在兩個參數列表
// 第一個參數列表為 : 零值(初始值)
// 第二個參數列表為: 簡化規則
// fold底層其實為foldLeft
val i = list.foldLeft(1)((x,y)=>x-y)
val i1 = list.foldRight(10)((x,y)=>x-y)
println(i)
println(i1)
}
}
(2)案例實操:兩個集合合並
object TestFold {
def main(args: Array[String]): Unit = {
// 兩個Map的數據合並
val map1 = mutable.Map("a"->1, "b"->2, "c"->3)
val map2 = mutable.Map("a"->4, "b"->5, "d"->6)
val map3: mutable.Map[String, Int] = map2.foldLeft(map1) {
(map, kv) => {
val k = kv._1
val v = kv._2
map(k) = map.getOrElse(k, 0) + v
map
}
}
println(map3)
}
}
7.7.5 普通WordCount案例
1)需求
單詞計數:將集合中出現的相同的單詞,進行計數,取計數排名前三的結果
2)需求分析
3)案例實操
object TestWordCount {
def main(args: Array[String]): Unit = {
// 單詞計數:將集合中出現的相同的單詞,進行計數,取計數排名前三的結果
val stringList = List("Hello Scala Hbase kafka", "Hello Scala Hbase", "Hello Scala", "Hello")
// 1) 將每一個字符串轉換成一個一個單詞
val wordList: List[String] = stringList.flatMap(str=>str.split(" "))
//println(wordList)
// 2) 將相同的單詞放置在一起
val wordToWordsMap: Map[String, List[String]] = wordList.groupBy(word=>word)
//println(wordToWordsMap)
// 3) 對相同的單詞進行計數
// (word, list) => (word, count)
val wordToCountMap: Map[String, Int] = wordToWordsMap.map(tuple=>(tuple._1, tuple._2.size))
// 4) 對計數完成后的結果進行排序(降序)
val sortList: List[(String, Int)] = wordToCountMap.toList.sortWith {
(left, right) => {
left._2 > right._2
}
}
// 5) 對排序后的結果取前3名
val resultList: List[(String, Int)] = sortList.take(3)
println(resultList)
}
}
7.7.6 復雜WordCount案例
1)方式一
object TestWordCount {
def main(args: Array[String]): Unit = {
// 第一種方式(不通用)
val tupleList = List(("Hello Scala Spark World ", 4), ("Hello Scala Spark", 3), ("Hello Scala", 2), ("Hello", 1))
val stringList: List[String] = tupleList.map(t=>(t._1 + " ") * t._2)
//val words: List[String] = stringList.flatMap(s=>s.split(" "))
val words: List[String] = stringList.flatMap(_.split(" "))
//在map中,如果傳進來什么就返回什么,不要用_省略
val groupMap: Map[String, List[String]] = words.groupBy(word=>word)
//val groupMap: Map[String, List[String]] = words.groupBy(_)
// (word, list) => (word, count)
val wordToCount: Map[String, Int] = groupMap.map(t=>(t._1, t._2.size))
val wordCountList: List[(String, Int)] = wordToCount.toList.sortWith {
(left, right) => {
left._2 > right._2
}
}.take(3)
//tupleList.map(t=>(t._1 + " ") * t._2).flatMap(_.split(" ")).groupBy(word=>word).map(t=>(t._1, t._2.size))
println(wordCountList)
}
}
2)方式二
object TestWordCount {
def main(args: Array[String]): Unit = {
val tuples = List(("Hello Scala Spark World", 4), ("Hello Scala Spark", 3), ("Hello Scala", 2), ("Hello", 1))
// (Hello,4),(Scala,4),(Spark,4),(World,4)
// (Hello,3),(Scala,3),(Spark,3)
// (Hello,2),(Scala,2)
// (Hello,1)
val wordToCountList: List[(String, Int)] = tuples.flatMap {
t => {
val strings: Array[String] = t._1.split(" ")
strings.map(word => (word, t._2))
}
}
// Hello, List((Hello,4), (Hello,3), (Hello,2), (Hello,1))
// Scala, List((Scala,4), (Scala,3), (Scala,2)
// Spark, List((Spark,4), (Spark,3)
// Word, List((Word,4))
val wordToTupleMap: Map[String, List[(String, Int)]] = wordToCountList.groupBy(t=>t._1)
val stringToInts: Map[String, List[Int]] = wordToTupleMap.mapValues {
datas => datas.map(t => t._2)
}
stringToInts
/*
val wordToCountMap: Map[String, List[Int]] = wordToTupleMap.map {
t => {
(t._1, t._2.map(t1 => t1._2))
}
}
val wordToTotalCountMap: Map[String, Int] = wordToCountMap.map(t=>(t._1, t._2.sum))
println(wordToTotalCountMap)
*/
}
}
7.8 隊列
1)說明
Scala也提供了隊列(Queue)的數據結構,隊列的特點就是先進先出。進隊和出隊的方法分別為enqueue和dequeue。
2)案例實操
object TestQueue {
def main(args: Array[String]): Unit = {
val que = new mutable.Queue[String]()
que.enqueue("a", "b", "c")
println(que.dequeue())
println(que.dequeue())
println(que.dequeue())
}
}
7.9 並行集合
1)說明
Scala為了充分使用多核CPU,提供了並行集合(有別於前面的串行集合),用於多核環境的並行計算。
2)案例實操
object TestPar {
def main(args: Array[String]): Unit = {
val result1 = (0 to 100).map{case _ => Thread.currentThread.getName}
val result2 = (0 to 100).par.map{case _ => Thread.currentThread.getName}
println(result1)
println(result2)
}
}
第8章 模式匹配
Scala中的模式匹配類似於Java中的switch語法
int i = 10
switch (i) {
case 10 :
System.out.println("10");
break;
case 20 :
System.out.println("20");
break;
default :
System.out.println("other number");
break;
}
但是scala從語法中補充了更多的功能,所以更加強大。
8.1 基本語法
模式匹配語法中,采用match關鍵字聲明,每個分支采用case關鍵字進行聲明,當需要匹配時,會從第一個case分支開始,如果匹配成功,那么執行對應的邏輯代碼,如果匹配不成功,繼續執行下一個分支進行判斷。如果所有case都不匹配,那么會執行case _分支,類似於Java中default語句。
object TestMatchCase {
def main(args: Array[String]): Unit = {
var a: Int = 10
var b: Int = 20
var operator: Char = 'd'
var result = operator match {
case '+' => a + b
case '-' => a - b
case '*' => a * b
case '/' => a / b
case _ => "illegal"
}
println(result)
}
}
1)說明
(1)如果所有case都不匹配,那么會執行case _ 分支,類似於Java中default語句,若此時沒有case _ 分支,那么會拋出MatchError。
(2)每個case中,不需要使用break語句,自動中斷case。
(3)match case語句可以匹配任何類型,而不只是字面量。
(4)=> 后面的代碼塊,直到下一個case語句之前的代碼是作為一個整體執行,可以使用{}括起來,也可以不括。
8.2 模式守衛
1)說明
如果想要表達匹配某個范圍的數據,就需要在模式匹配中增加條件守衛。
2)案例實操
object TestMatchGuard {
def main(args: Array[String]): Unit = {
def abs(x: Int) = x match {
case i: Int if i >= 0 => i
case j: Int if j < 0 => -j
case _ => "type illegal"
}
println(abs(-5))
}
}
8.3 模式匹配類型
8.3.1 匹配常量
1)說明
Scala中,模式匹配可以匹配所有的字面量,包括字符串,字符,數字,布爾值等等。
2)實操
object TestMatchVal {
def main(args: Array[String]): Unit = {
println(describe(6))
}
def describe(x: Any) = x match {
case 5 => "Int five"
case "hello" => "String hello"
case true => "Boolean true"
case '+' => "Char +"
}
}
8.3.2 匹配類型
1)說明
需要進行類型判斷時,可以使用前文所學的isInstanceOf[T]和asInstanceOf[T],也可使用模式匹配實現同樣的功能。
2)案例實操
object TestMatchClass {
def describe(x: Any) = x match {
case i: Int => "Int"
case s: String => "String hello"
case m: List[_] => "List"
case c: Array[Int] => "Array[Int]"
case someThing => "something else " + someThing
}
def main(args: Array[String]): Unit = {
//泛型擦除
println(describe(List(1, 2, 3, 4, 5)))
//數組例外,可保留泛型
println(describe(Array(1, 2, 3, 4, 5, 6)))
println(describe(Array("abc")))
}
}
8.3.3 匹配數組
1)說明
scala模式匹配可以對集合進行精確的匹配,例如匹配只有兩個元素的、且第一個元素為0的數組。
2)案例實操
object TestMatchArray {
def main(args: Array[String]): Unit = {
for (arr <- Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1), Array("hello", 90))) { // 對一個數組集合進行遍歷
val result = arr match {
case Array(0) => "0" //匹配Array(0) 這個數組
case Array(x, y) => x + "," + y //匹配有兩個元素的數組,然后將將元素值賦給對應的x,y
case Array(0, _*) => "以0開頭的數組" //匹配以0開頭和數組
case _ => "something else"
}
println("result = " + result)
}
}
}
8.3.4 匹配列表
1)方式一
object TestMatchList {
def main(args: Array[String]): Unit = {
//list是一個存放List集合的數組
//請思考,如果要匹配 List(88) 這樣的只含有一個元素的列表,並原值返回.應該怎么寫
for (list <- Array(List(0), List(1, 0), List(0, 0, 0), List(1, 0, 0), List(88))) {
val result = list match {
case List(0) => "0" //匹配List(0)
case List(x, y) => x + "," + y //匹配有兩個元素的List
case List(0, _*) => "0 ..."
case _ => "something else"
}
println(result)
}
}
}
2)方式二
object TestMatchList {
def main(args: Array[String]): Unit = {
val list: List[Int] = List(1, 2, 5, 6, 7)
list match {
case first :: second :: rest => println(first + "-" + second + "-" + rest)
case _ => println("something else")
}
}
}
8.3.5 匹配元組
object TestMatchTuple {
def main(args: Array[String]): Unit = {
//對一個元組集合進行遍歷
for (tuple <- Array((0, 1), (1, 0), (1, 1), (1, 0, 2))) {
val result = tuple match {
case (0, _) => "0 ..." //是第一個元素是0的元組
case (y, 0) => "" + y + "0" // 匹配后一個元素是0的對偶元組
case (a, b) => "" + a + " " + b
case _ => "something else" //默認
}
println(result)
}
}
}
擴展案例 object TestGeneric {
|
8.3.6 匹配對象及樣例類
1)基本語法
class User(val name: String, val age: Int)
object User{
def apply(name: String, age: Int): User = new User(name, age)
def unapply(user: User): Option[(String, Int)] = {
if (user == null)
None
else
Some(user.name, user.age)
}
}
object TestMatchUnapply {
def main(args: Array[String]): Unit = {
val user: User = User("zhangsan", 11)
val result = user match {
case User("zhangsan", 11) => "yes"
case _ => "no"
}
println(result)
}
}
小結
- val user = User("zhangsan",11),該語句在執行時,實際調用的是User伴生對象中的apply方法,因此不用new關鍵字就能構造出相應的對象。
- 當將User("zhangsan", 11)寫在case后時[case User("zhangsan", 11) => "yes"],會默認調用unapply方法(對象提取器),user作為unapply方法的參數,unapply方法將user對象的name和age屬性提取出來,與User("zhangsan", 11)中的屬性值進行匹配
- case中對象的unapply方法(提取器)返回Some,且所有屬性均一致,才算匹配成功,屬性不一致,或返回None,則匹配失敗。
- 若只提取對象的一個屬性,則提取器為unapply(obj:Obj):Option[T]
若提取對象的多個屬性,則提取器為unapply(obj:Obj):Option[(T1,T2,T3…)]
若提取對象的可變個屬性,則提取器為unapplySeq(obj:Obj):Option[Seq[T]]
2)樣例類
(1)語法:
case class Person (name: String, age: Int)
(2)說明
1樣例類仍然是類,和普通類相比,只是其自動生成了伴生對象,並且伴生對象中自動提供了一些常用的方法,如apply、unapply、toString、equals、hashCode和copy。
2樣例類是為模式匹配而優化的類,因為其默認提供了unapply方法,因此,樣例類可以直接使用模式匹配,而無需自己實現unapply方法。
3構造器中的每一個參數都成為val,除非它被顯式地聲明為var(不建議這樣做)
(3)實操
上述匹配對象的案例使用樣例類會節省大量代碼
case class User(name: String, age: Int)
object TestMatchUnapply {
def main(args: Array[String]): Unit = {
val user: User = User("zhangsan", 11)
val result = user match {
case User("zhangsan", 11) => "yes"
case _ => "no"
}
println(result)
}
}
8.4 變量聲明中的模式匹配
case class Person(name: String, age: Int)
object TestMatchVariable {
def main(args: Array[String]): Unit = {
val (x, y) = (1, 2)
println(s"x=$x,y=$y")
val Array(first, second, _*) = Array(1, 7, 2, 9)
println(s"first=$first,second=$second")
val Person(name, age) = Person1("zhangsan", 16)
println(s"name=$name,age=$age")
}
}
8.5 for表達式中的模式匹配
object TestMatchFor {
def main(args: Array[String]): Unit = {
val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
for ((k, v) <- map) { //直接將map中的k-v遍歷出來
println(k + " -> " + v) //3個
}
println("----------------------")
//遍歷value=0的 k-v ,如果v不是0,過濾
for ((k, 0) <- map) {
println(k + " --> " + 0) // B->0
}
println("----------------------")
//if v == 0 是一個過濾的條件
for ((k, v) <- map if v >= 1) {
println(k + " ---> " + v) // A->1 和 c->33
}
}
}
8.6 偏函數中的模式匹配(了解)
偏函數也是函數的一種,通過偏函數我們可以方便的對輸入參數做更精確的檢查。例如該偏函數的輸入類型為List[Int],而我們需要的是第一個元素是0的集合,這就是通過模式匹配實現的。
1) 偏函數定義
val second: PartialFunction[List[Int], Option[Int]] = {
case x :: y :: _ => Some(y)
}
注:該偏函數的功能是返回輸入的List集合的第二個元素
2)偏函數原理
上述代碼會被scala編譯器翻譯成以下代碼,與普通函數相比,只是多了一個用於參數檢查的函數——isDefinedAt,其返回值類型為Boolean。
val second = new PartialFunction[List[Int], Option[Int]] {
//檢查輸入參數是否合格
override def isDefinedAt(list: List[Int]): Boolean = list match {
case x :: y :: _ => true
case _ => false
}
//執行函數邏輯
override def apply(list: List[Int]): Option[Int] = list match {
case x :: y :: _ => Some(y)
}
}
3)偏函數使用
偏函數不能像second(List(1,2,3))這樣直接使用,因為這樣會直接調用apply方法,而應該調用applyOrElse方法,如下
second.applyOrElse(List(1,2,3), (_: List[Int]) => None)
applyOrElse方法的邏輯為 if (ifDefinedAt(list)) apply(list) else default。如果輸入參數滿足條件,即isDefinedAt返回true,則執行apply方法,否則執行defalut方法,default方法為參數不滿足要求的處理邏輯。
4) 案例實操
(1)需求
將該List(1,2,3,4,5,6,"test")中的Int類型的元素加一,並去掉字符串。
def main(args: Array[String]): Unit = {
val list = List(1,2,3,4,5,6,"test")
val list1 = list.map {
a =>
a match {
case i: Int => i + 1
case s: String =>s + 1
}
}
println(list1.filter(a=>a.isInstanceOf[Int]))
}
(2)實操
方法一:
List(1,2,3,4,5,6,"test").filter(_.isInstanceOf[Int]).map(_.asInstanceOf[Int] + 1).foreach(println)
方法二:
List(1, 2, 3, 4, 5, 6, "test").collect { case x: Int => x + 1 }.foreach(println)
第9章 異常
語法處理上和Java類似,但是又不盡相同。
9.1 Java異常處理
public class ExceptionDemo {
public static void main(String[] args) {
try {
int a = 10;
int b = 0;
int c = a / b;
}catch (ArithmeticException e){
// catch時,需要將范圍小的寫到前面
e.printStackTrace();
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("finally");
}
}
}
注意事項
(1)Java語言按照try—catch—finally的方式來處理異常
(2)不管有沒有異常捕獲,都會執行finally,因此通常可以在finally代碼塊中釋放資源。
(3)可以有多個catch,分別捕獲對應的異常,這時需要把范圍小的異常類寫在前面,把范圍大的異常類寫在后面,否則編譯錯誤。
9.2 Scala異常處理
def main(args: Array[String]): Unit = {
try {
var n= 10 / 0
}catch {
case ex: ArithmeticException=>{
// 發生算術異常
println("發生算術異常")
}
case ex: Exception=>{
// 對異常處理
println("發生了異常1")
println("發生了異常2")
}
}finally {
println("finally")
}
}
1)我們將可疑代碼封裝在try塊中。在try塊之后使用了一個catch處理程序來捕獲異常。如果發生任何異常,catch處理程序將處理它,程序將不會異常終止。
2)Scala的異常的工作機制和Java一樣,但是Scala沒有“checked(編譯期)”異常,即Scala沒有編譯異常這個概念,異常都是在運行的時候捕獲處理。
3)異常捕捉的機制與其他語言中一樣,如果有異常發生,catch子句是按次序捕捉的。因此,在catch子句中,越具體的異常越要靠前,越普遍的異常越靠后,如果把越普遍的異常寫在前,把具體的異常寫在后,在Scala中也不會報錯,但這樣是非常不好的編程風格。
4)finally子句用於執行不管是正常處理還是有異常發生時都需要執行的步驟,一般用於對象的清理工作,這點和Java一樣。
5)用throw關鍵字,拋出一個異常對象。所有異常都是Throwable的子類型。throw表達式是有類型的,就是Nothing,因為Nothing是所有類型的子類型,所以throw表達式可以用在需要類型的地方
def test():Nothing = {
throw new Exception("不對")
}
6)java提供了throws關鍵字來聲明異常。可以使用方法定義聲明異常。它向調用者函數提供了此方法可能引發此異常的信息。它有助於調用函數處理並將該代碼包含在try-catch塊中,以避免程序異常終止。在Scala中,可以使用throws注解來聲明異常
def main(args: Array[String]): Unit = {
f11()
}
@throws(classOf[NumberFormatException])
def f11()={
"abc".toInt
}
第10章 隱式轉換
當編譯器第一次編譯失敗的時候,會在當前的環境中查找能讓代碼編譯通過的方法,用於將類型進行轉換,實現二次編譯
10.1 隱式函數
1)說明
隱式轉換可以在不需改任何代碼的情況下,擴展某個類的功能。
2)案例實操
需求:通過隱式轉化為Int類型增加方法。
class MyRichInt(val self: Int) {
def myMax(i: Int): Int = {
if (self < i) i else self
}
def myMin(i: Int): Int = {
if (self < i) self else i
}
}
object TestImplicitFunction {
// 使用implicit關鍵字聲明的函數稱之為隱式函數
implicit def convert(arg: Int): MyRichInt = {
new MyRichInt(arg)
}
def main(args: Array[String]): Unit = {
// 當想調用對象功能時,如果編譯錯誤,那么編譯器會嘗試在當前作用域范圍內查找能調用對應功能的轉換規則,這個調用過程是由編譯器完成的,所以稱之為隱式轉換。也稱之為自動轉換
println(2.myMax(6))
}
}
10.2 隱式參數
普通方法或者函數中的參數可以通過implicit關鍵字聲明為隱式參數,調用該方法時,就可以傳入該參數,編譯器會在相應的作用域尋找符合條件的隱式值。
1)說明
(1)同一個作用域中,相同類型的隱式值只能有一個
(2)編譯器按照隱式參數的類型去尋找對應類型的隱式值,與隱式值的名稱無關。
(3)隱式參數優先於默認參數
2)案例實操
object TestImplicitParameter {
implicit val str: String = "hello world!"
def hello(implicit arg: String="good bey world!"): Unit = {
println(arg)
}
def main(args: Array[String]): Unit = {
hello
}
}
10.3 隱式類
在Scala2.10后提供了隱式類,可以使用implicit聲明類,隱式類的非常強大,同樣可以擴展類的功能,在集合中隱式類會發揮重要的作用。
1)隱式類說明
(1)其所帶的構造參數有且只能有一個
(2)隱式類必須被定義在“類”或“伴生對象”或“包對象”里,即隱式類不能是頂級的。
2)案例實操
object TestImplicitClass {
implicit class MyRichInt(arg: Int) {
def myMax(i: Int): Int = {
if (arg < i) i else arg
}
def myMin(i: Int) = {
if (arg < i) arg else i
}
}
def main(args: Array[String]): Unit = {
println(1.myMax(3))
}
}
10.4 隱式解析機制
1)說明
(1)首先會在當前代碼作用域下查找隱式實體(隱式方法、隱式類、隱式對象)。(一般是這種情況)
(2)如果第一條規則查找隱式實體失敗,會繼續在隱式參數的類型的作用域里查找。類型的作用域是指與該類型相關聯的全部伴生對象以及該類型所在包的包對象。
2)案例實操
package com.atguigu.chapter10
import com.atguigu.chapter10.Scala05_Transform4.Teacher
//(2)如果第一條規則查找隱式實體失敗,會繼續在隱式參數的類型的作用域里查找。類型的作用域是指與該類型相關聯的全部伴生模塊,
object TestTransform extends PersonTrait {
def main(args: Array[String]): Unit = {
//(1)首先會在當前代碼作用域下查找隱式實體
val teacher = new Teacher()
teacher.eat()
teacher.say()
}
class Teacher {
def eat(): Unit = {
println("eat...")
}
}
}
trait PersonTrait {
}
object PersonTrait {
// 隱式類 : 類型1 => 類型2
implicit class Person5(user:Teacher) {
def say(): Unit = {
println("say...")
}
}
}
第11章 泛型
11.1 協變和逆變
1)語法
class MyList[+T]{ //協變
}
class MyList[-T]{ //逆變
}
class MyList[T] //不變
2)說明
協變:Son是Father的子類,則MyList[Son] 也作為MyList[Father]的“子類”。
逆變:Son是Father的子類,則MyList[Son]作為MyList[Father]的“父類”。
不變:Son是Father的子類,則MyList[Father]與MyList[Son]“無父子關系”。
3)實操
//泛型模板
//class MyList<T>{}
//不變
//class MyList[T]{}
//協變
//class MyList[+T]{}
//逆變
//class MyList[-T]{}
class Parent{}
class Child extends Parent{}
class SubChild extends Child{}
object Scala_TestGeneric {
def main(args: Array[String]): Unit = {
//var s:MyList[Child] = new MyList[SubChild]
}
}
11.2 泛型上下限
1)語法
Class PersonList[T <: Person]{ //泛型上限
}
Class PersonList[T >: Person]{ //泛型下限
}
2)說明
泛型的上下限的作用是對傳入的泛型進行限定。
3)實操
class Parent{}
class Child extends Parent{}
class SubChild extends Child{}
object Scala_TestGeneric {
def main(args: Array[String]): Unit = {
//test(classOf[SubChild])
//test[Child](new SubChild)
}
//泛型通配符之上限
//def test[A <: Child](a:Class[A]): Unit ={
// println(a)
//}
//泛型通配符之下限
//def test[A >: Child](a:Class[A]): Unit ={
// println(a)
//}
//泛型通配符之下限 形式擴展
def test[A >: Child](a:A): Unit ={
println(a.getClass.getName)
}
}
11.3 上下文限定
1)語法
def f[A : B](a: A) = println(a) //等同於def f[A](a:A)(implicit arg:B[A])=println(a)
2)說明
上下文限定是將泛型和隱式轉換的結合產物,以下兩者功能相同,使用上下文限定[A : Ordering]之后,方法內無法使用隱式參數名調用隱式參數,需要通過implicitly[Ordering[A]]獲取隱式變量,如果此時無法查找到對應類型的隱式變量,會發生出錯誤。
implicit val x = 1
val y = implicitly[Int]
val z = implicitly[Double]
3)實操
def f[A:Ordering](a:A,b:A) =implicitly[Ordering[A]].compare(a,b)
def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)
第12章 總結
12.1 開發環境
要求掌握必要的Scala開發環境搭建技能。
12.2 變量和數據類型
掌握var和val的區別
掌握數值類型(Byte、Short、Int、Long、Float、Double、Char)之間的轉換關系
12.3 流程控制
掌握if-else、for、while等必要的流程控制結構,掌握如何實現break、continue的功能。
12.4 函數式編程
掌握高階函數、匿名函數、函數柯里化、閉包、函數參數以及函數至簡原則。
12.5 面向對象
掌握Scala與Java繼承方面的區別、單例對象(伴生對象)、構造方法、特質的用法及功能。
12.6 集合
掌握常用集合的使用、集合常用的計算函數。
12.7 模式匹配
掌握模式匹配的用法
12.8 下划線
掌握下划線不同場合的不同用法
12.9 異常
掌握異常常用操作即可
12.10 隱式轉換
掌握隱式方法、隱式參數、隱式類,以及隱式解析機制
12.11 泛型
掌握泛型語法
第13章 IDEA快捷鍵
1)快速生成程序入口:main
輸入main->回車
def main(args: Array[String]): Unit = {
}
2)自動補全變量:.var
輸入1.var->回車
val i: Int = 2
3)快速打印:.sout
輸入1.sout->回車
println(1)
4)快速生成for循環:遍歷對象.for
輸入1 to 3.for
for (elem <- 1 to 3) {
}
5) 查看當前文件的結構:Ctrl + F12
6) 格式化當前代碼:Ctrl + Shift + L
7) 自動為當前代碼補全變量聲明:Ctrl + Shift + V
更多請查看截圖: