1、變量聲明
1.1、 聲明val變量
例如,val result = 1 + 1
後續這些常量是可以繼續使用的,例如,2 * result
但是注意常量聲明後,是無法改變它的值的,例如,result = 1,會返回error: reassignment to val的錯誤信
1.2、 聲明var變量
如果要聲明值可以改變的引用,可以使用var變量。例如,val result = 1,result = 2。但是在scala程序中,通常建議使用val,也就是常量,因為在網絡傳輸中,如果是變量,可能會擔心值被修改
1.3、 指定類型
無論聲明val變量,還是聲明var變量,都可以手動指定其類型,如果不指定的話,scala會自動根據值,進行類型的推斷
例如,val name: String = null
例如,val name: Any = "Tom"
聲明多個變量:可以將多個變量放在一起進行聲明。
例如,val name1, name2:String = null
例如,val num1, num2 = 100
2、基本類型
基本數據類型:Byte、Char、Short、Int、Long、Float、Double、Boolean
注意 : scala沒有基本數據類型與包裝類型的概念,統一都是類。scala自己會負責基本數據類型和引用類型的轉換操作
包裝類型都是可以這樣調用 : 例如,100.toString(),1.to(10)
2.1、類型的加強版
例如,String類通過StringOps類增強了大量的函數,"Hello".intersect(" World")
例如,Scala還提供了RichInt、RichDouble、RichChar等類型,RichInt就提供了to函數,1.to(10),此處Int先隱式轉換為RichInt,然後再調用其to函數
2.2、基本操作符
scala的算術操作符與java的算術操作符也沒有什麼區別,比如+、-、*、/、%等,以及&、|、^、>>、<<等
注意,在scala中,這些操作符其實是數據類型的函數,比如1 + 1,可以寫做1.+(1),例如,1.to(10),又可以寫做1 to 10
scala中沒有提供++、--操作符,我們只能使用+和-,比如counter++是錯誤的,必須寫做counter += 1
3、函數調用與apply()函數
3.1、函數調用方式
在scala中,函數調用如下 :
例如,import scala.math._,sqrt(2),pow(2, 4),min(3, Pi)
如果調用函數時,不需要傳遞參數,則scala允許調用函數時省略括號的,例如,"Hello World".distinct
3.2、apply函數
Scala中的apply函數是非常特殊的一種函數,在Scala的object中(半生對象),可以聲明apply函數。而使用“類名()”的形式,其實就是“類名.apply()”的一種縮寫。通常使用這種方式來構造類的對象,而不是使用“new 類名()”的方式
例如,Array(1, 2, 3, 4),實際上是用Array object的apply()函數來創建Array類的實例,也就是一個數組
4、條件控制與循環
4.1、if表達式
if表達式的定義 :
在Scala中,if表達式是有值的,就是if或者else中最後一行語句返回的值
if表達式賦予一個變量,例如,val isAdult = if (age > 18) 1 else 0
if表達式的類型推斷:
由於if表達式是有值的,而if和else子句的值類型可能不同,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表達式通常需要放在多行。可以使用{}的方式
result = if (age > 18) {
println("成年人")
"aduit"
} else if (age > 12) {
println("未成年")
"teenager"
} else {
println("兒童")
"child"
}
4.2、語句終結符、塊表達式
默認情況下,Scala不需要語句終結符,默認將每一行作為一個語句
一行多條語句,就必須使用語句終結符;,比如 var a, b, c = 0; if(a < 10) { b = b + 1; c = c + 1 },對於多行語句,還是會適應花括號的方式
if(a < 10) {
b = b + 1
c = c + 1
}
塊表達式,塊表達式,指的就是{}中的值,其中可以包含多條語句,最後一個語句的值就是塊表達式的返回值
val a = 9
var b = 0
val result = if (a < 10) {
b += 1
a + 1
} else {
b -= 1
a - 1
}
最終 result = 10
4.3、輸入和輸出
print和println:print打印時不會加換行符,而println打印時會加一個換行符
printf: printf可以用於進行格式化(和C中一樣)
例如 : printf("Hi, my name is %s, I'm %d years old.", "Tom", 30)
readLine: readLine允許我們從控制枱讀取用户輸入的數據,類似於java中的System.in和Scanner的作用
println("Your names is : ")
val name: String = scala.io.StdIn.readLine()
println("Your age is : ")
val age: Int = scala.io.StdIn.readInt()
println(
s"""
| name : ${name}
| age : ${age}
|""".stripMargin)
輸出結果 :
Your names is :
zhangsan
Your age is :
30
name : zhangsan
age : 30
4.4、循環
while do循環 : Scala有while do循環,基本與Java相同
var n = 10
while (n > 0) {
println(n)
n -= 1
}
Scala沒有for循環,只能使用while替代for循環,或者使用簡易版的for語句
val count = 10
for (num <- 1 to count)
println(num)
// 1 until count,説明不能到count
for (num <- 1 until count)
println(num)
Scala沒有提供類似於Java中的break語句,但是可以使用boolean、return或者Breaks的break函數來替代
for (num <- 1 to count) {
if (num == 5) {
return
}
println("num : " + num)
}
4.5、多重循環
冒泡排序 :
val arrs: Array[Int] = Array(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
for (i <- 0 until(10); j <- 0 until(9 - i)) {
if (arrs(j) > arrs(j + 1)) {
var temp = arrs(j)
arrs(j) = arrs(j + 1)
arrs(j + 1) = temp
}
}
println(arrs.mkString(","))
if守衞 : 取偶數
for (k <- 1 to 10 if k % 2 == 0) println(k)
for推導式:構建集合
val ints: immutable.IndexedSeq[Int] = for (i <- 1 to 10) yield i
5、函數入門
5.1、函數的定義與調用
在Scala中定義函數時,需要定義函數的函數名、參數、函數體
def main(args: Array[String]): Unit = {
sayHello("Tom", 30)
}
def sayHello(name : String, age : Int) = {
if (age > 18) {
printf("hi %s,you are adult\n", name)
} else {
printf("hi %s,you are not adult\n", name)
}
}
Scala要求必須給出所有參數的類型,但是不一定給出函數返回值的類型,只要右側的函數體中不包含遞歸的語句,Scala就可以自己根據右側的表達式推斷出返回類型
單行函數 :
def sayHello2(name : String) = println("hello " + name)
如果函數體有多行代碼,可以使用代碼塊的方式包裹多行代碼,代碼塊中最後一行的返回值就是整個函數的返回值
累加功能:
def sum(n : Int) = {
var sum = 0
for (i <- 1 to n) sum += i
sum
}
5.2、遞歸函數與返回類型
如果在函數體內遞歸調用函數自身,則必須手動給出函數的返回類型
例如,實現經典的斐波那契數列:
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)
}
5.3、默認參數
在Scala中,有時我們調用某些函數時,不希望給出參數的具體值,而希望使用參數自身默認的值,此時就定義在定義函數時使用默認參數
def say(name : String, age : Int, hobby : String = "coding")= {
s"my name is ${name}, age is : ${age}, hobby is : ${hobby}"
}
如果給出的參數不夠,則會從作往右依次應用參數
5.4、帶名參數
在調用函數時,也可以不按照函數定義的參數順序來傳遞參數,而是使用帶名參數的方式來傳遞
比如 : println(say(age = 30, name = "zhangsan", hobby = "hehe"))
還可以混合使用未命名參數和帶名參數,但是未命名參數必須排在帶名參數前面
println(say("zhagnsan", age = 20))
5.5、變長參數
在Scala中,有時我們需要將函數定義為參數個數可變的形式,則此時可以使用變長參數定義函數
def sum(nums: Int*) = {
var result = 0
for (num <- nums) result += num
result
}
使用序列調用變長參數
在如果想要將一個已有的序列直接調用變長參數函數,是不對的。比如val s = sum(1 to 5)。此時需要使用Scala特殊的語法將參數定義為序列,讓Scala解釋器能夠識別
val s = sum(1 to 5: _*)
案例:使用遞歸函數實現累加
def sum2(nums: Int*): Int = {
if (nums.length == 0) 0
else nums.head + sum2(nums.tail: _*)
}
5.6、過程
在Scala中,定義函數時,如果函數體直接包裹在了花括號裏面,而沒有使用=連接,則函數的返回值類型就是Unit。這樣的函數就被稱之為過程。過程通常用於不需要返回值的函數
def sayHello4(name : String){
println("Hello," + name)
}
def sayHello5(name: String): Unit = {
println("Hello," + name)
}
6、lazy值
在Scala中,提供了lazy值的特性,也就是説,如果將一個變量聲明為lazy,則只有在第一次使用該變量時,變量對應的表達式才會發生計算。這種特性對於特別耗時的計算操作特別有用,比如打開文件進行IO,進行網絡IO等
import scala.io.Source._
// datas/user1.json文件是不存在的,也不會保持,只有到真正使用運行的時候才會報錯
lazy val lines1: Iterator[String] = fromFile("datas/user1.json").getLines()
val lines2: Iterator[String] = fromFile("datas/user.json").getLines()
7、異常
使用match case的方式進行異常匹配
try {
throw new IOException("user defined exception.")
} catch {
case e : IllegalArgumentException => println("illegal argument")
case e : IOException => println("illegal argument")
case _ => println("other exception.")
}
8、Array & ArrayBuffer
8.1、Array
在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("tom", 30)
8.2、ArrayBuffer
在Scala中,如果需要類似於Java中的ArrayList這種長度可變的集合類,則可以使用ArrayBuffer
val arrayBuffer = ArrayBuffer[String]()
// += 就是加入一個元素
arrayBuffer += "zhagnsan"
arrayBuffer += "lisi"
arrayBuffer += "wangwu"
// -= 就是去掉一個元素
arrayBuffer -= "zhagnsan"
val arrayBuffer2 = ArrayBuffer[String]("aa", "bb")
// 這個其實相當於Java中的addAll
arrayBuffer ++= arrayBuffer2
for (item <- arrayBuffer) {
println(item)
}
結果輸出 :
lisi
wangwu
aa
bb
// 使用trimEnd()函數,可以從尾部截斷指定個數的元素
arrayBuffer.trimEnd(2)
println(arrayBuffer)
結果輸出 :
ArrayBuffer(lisi, wangwu)
其他功能 :
// 使用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
8.3、遍歷
// 使用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
9、數組操作之數組轉換
9.1、使用yield方式
// 對Array進行轉換,獲取的還是Array
val arr1 = Array(1,2,3,4,5)
val arr2 = for (ele <- arr1) yield ele * ele
// 對ArrayBuffer進行轉換,獲取的還是ArrayBuffer
val arr3 = ArrayBuffer[Int]()
arr3 += (1,2,3,4,5)
val arr4 = for (ele <- arr3) yield ele * ele
// 結合if守衞,僅轉換需要的元素
val arr5 = for (ele <- arr3 if ele % 2 == 0) yield ele * ele
9.2、函數式編程轉換數組
// 使用函數式編程轉換數組
val arr6: Array[Int] = arr1.filter(_ % 2 == 0).map(_ * 2)
10、Map與Tuple
10.1、Map
創建Map:
// 創建一個不可變的Map
val users1: Map[String, Int] = Map("zhangsan" -> 23, "lisi" -> 30, "wangwu" -> 40)
// users1("zhagnsan") = 20 因為Map默認是不可變的,所以這個是不能改變的
val users2: mutable.Map[String, Int] = scala.collection.mutable.Map("zhagnsan" -> 23, "lisi" -> 30, "wangwu" -> 40)
users2("zhagnsan") = 20
users2 += (("zhaoliu", 20))
// 使用另外一種方式來定義Map元素
val users3: Map[String, Int] = Map(("zhangsan", 20), ("lisi", 30), ("wangwu", 40))
// 創建一個空的HashMap
val users4 = new mutable.HashMap[String, Int]()
訪問Map的元素:
// 獲取指定key對應的value,如果key不存在,會報錯
val tomAge = ages("Tom")
// 使用contains函數檢查key是否存在
val tomAge = if (ages.contains("Tom")) ages("Tom") else 0
// getOrElse函數
val tomAge = ages.getOrElse("Tom", 0)
修改Map的元素:
// 更新Map的元素
users2("Tom") = 31
// 增加多個元素
users2 += ("Mike" -> 35, "Tom" -> 40)
// 移除元素
users2 -= "Mike"
// 更新不可變的map
val users5 = users1 + ("Mike" -> 36, "Tom" -> 40)
// 移除不可變map的元素
val users6 = users1 - "Tom"
遍歷Map :
// 遍歷map的entrySet
for ((key, value) <- users1) println(key + " " + value)
// 遍歷map的key
for (key <- users1.keySet) println(key)
// 遍歷map的value
for (value <- users1.values) println(value)
// 生成新map,反轉key和value
for ((key, value) <- users1) yield (value, key)
10.2、SortedMap和LinkedHashMap
// SortedMap可以自動對Map的key進行排序
val users5 = mutable.SortedMap("zhangsan" -> 30, "lisi" -> 34, "wangwu" -> 45)
val user6 = new mutable.LinkedHashMap[String, Int]
user6("zhangsan") = 30
user6("lisi") = 34
user6("wangwu") = 45
10.3、Tuple
其實就是簡單的鍵值對,簡單理解就是Map中的一個元素,可以通過_1、_2、_n...下標來訪問
val t = ("zhangsan", 20)
println(t._1)
println(t._2)
// zip操作
val names = Array("zhangsan", "lisi", "wagnwu")
val ages = Array(30, 20, 25)
val users = names.zip(ages)
for ((name, age) <- users) println(name + "->" + age)
11、面向對象
11.1、基礎
object User {
var user : User = new User
def apply(): User = {
user
}
def adjustSalary(sal : Int): Unit = {
user.salary = sal
}
def getName = user.name
def setName(name : String): Unit = {
user.name = name
}
def info = user.info()
}
class User {
// 國家,默認中國,不能改哈
private[this] val country = "中國"
// 薪水,薪水是不能隨便改的,比如説只能公司給你改
private var salary = 1000
// 姓名是可以修改的,自己修改即可
var name : String = _
def info(): Unit = {
println("username : " + name + ", your country is : " + country + ", salary is : " + salary)
}
}
調用處 :
// 普通用户操作
val commonUser = new User
commonUser.name = "zhangsan"
commonUser.info
// 管理用户操作
val adminUser = User
adminUser.setName("zhangsan")
adminUser.adjustSalary(10000)
adminUser.info
打印結果 :
username : zhangsan, your country is : 中國, salary is : 1000
username : zhangsan, your country is : 中國, salary is : 10000
注意 :
- 定義不帶private的var field,此時scala生成的面向JVM的類時,會定義為private的name字段,並提供public的getter和setter方法
- 而如果使用private修飾field,則生成的getter和setter也是private的,只有伴生對象可以訪問
- 如果定義val field,則只會生成getter方法
- 如果不希望生成setter和getter方法,則將field聲明為private[this],誰都修改不了
11.2、自定義getter與setter
Scala的getter和setter方法的命名與java是不同的,是field和field_=的方式
注意 :
如果只是希望擁有簡單的getter和setter方法,那麼就按照scala提供的語法規則,根據需求為field選擇合適的修飾符就好:var、val、private、private[this]
但是如果希望能夠自己對getter與setter進行控制,則可以自定義getter與setter方法
自定義setter方法的時候一定要注意scala的語法限制,簽名、=、參數間不能有空格
class Stu {
private var username: String = "wuming"
def name = "my name is :" + username
def name_=(newName:String) {
username = newName
}
}
調用處 :
val stu = new Stu
println(stu.name)
stu.name = "zhangsan"
println(stu.name)
11.3、僅暴露field的getter方法
如果你不希望field有setter方法,則可以定義為val,但是此時就再也不能更改field的值了
但是如果希望能夠僅僅暴露出一個getter方法,並且還能通過某些方法更改field的值,那麼需要綜合使用private以及自定義getter方法
此時,由於field是private的,所以setter和getter都是private,對外界沒有暴露;自己可以實現修改field值的方法;自己可以覆蓋getter方法
class Stu {
private var username: String = "wuming"
def name = "my name is :" + username
def updateName(newName:String) {
if (newName == "zhangsan") username = newName
else println("newName : " + newName + " deny")
}
}
11.4、private[this]的使用
如果將field使用private來修飾,那麼代表這個field是類私有的,在類的方法中,可以直接訪問類的其他對象的private field
這種情況下,如果不希望field被其他對象訪問到,那麼可以使用private[this],意味着對象私有的field,只有本對象內可以訪問到
11.4、Java風格的getter和setter方法
class Stu {
@BeanProperty var username: String = "wuming"
}
11.5、輔助constructor
Scala中,可以給類定義多個輔助constructor,類似於java中的構造函數重載。輔助constructor之間可以互相調用,而且必須第一行調用主constructor
class Stu {
private var name = ""
private var age = 0
def this(name : String) {
this()
this.name = name
}
def this(name : String, age : Int) {
this()
this.name = name
this.age = age
}
}
11.6、主constructor
Scala中,主constructor是與類名放在一起的,與java不同。而且類中,沒有定義在任何方法或者是代碼塊之中的代碼,就是主constructor的代碼
class Stu(val name : String, val age : Int) {
println(s"my name is : ${name}, age is : ${age}")
}
**主constructor中還可以通過使用默認參數,來指定參數的默認值
**
class Stu(val name : String = "Jerry", val age : Int = 30) {
println(s"my name is : ${name}, age is : ${age}")
}
注意 :
如果主constrcutor傳入的參數什麼修飾都沒有,比如name: String,那麼如果類內部的方法使用到了,則會聲明為private[this] name;否則沒有該field,就只能被constructor代碼使用而已
11.7、內部類
class Animal {
class Dog(val name : String) {}
val dogs = new ArrayBuffer[Dog]()
def getDog(name : String) = {
new Dog(name)
}
}
val animal = new Animal
val dog1: animal.Dog = animal.getDog("doudoud")
animal.dogs += dog1
println(animal.dogs)
11.8、object
object,相當於class的單個實例,通常在裏面放一些靜態的field或者method
第一次調用object的方法時,就會執行object的constructor,也就是object內部不在method中的代碼(其實就是想説靜態代碼塊);但是object不能定義接受參數的constructor
注意,object的constructor只會在其第一次被調用時執行一次,以後再次調用就不會再次執行constructor了。object通常用於作為單例模式的實現,或者放class的靜態成員,比如工具方法
object Person {
private var eyeNum = 2
println("this Person object.")
def apply() = new Person("xx")
def getEyeNum = eyeNum
}
class Person(val username:String) {
println(s"username : ${username}")
}
調用 :
val person = Person()
結果顯示 :
this Person object.
username : xx
11.9、伴生對象
如果有一個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("my name : " + name + "; age is : " + age + "; eyeNum : " + Person.eyeNum)
}
11.10、object繼承抽象類
object的功能其實和class類似,除了不能定義接受參數的constructor之外。object也可以繼承抽象類,並覆蓋抽象類中的方法
abstract class UserService (var token : String){
def addUser(username : String) :Unit
}
object UserServiceImpl extends UserService ("xxxx111"){
override def addUser(username: String) = {
println(token + ":" + username)
}
}
11.11、apply方法
object中非常重要的一個特殊方法,就是apply方法。通常在伴生對象中實現apply方法,並在其中實現構造伴生類的對象的功能而創建伴生類的對象時,通常不會使用new Class的方式,而是使用Class()的方式,隱式地調用伴生對象得apply方法,這樣會讓對象創建更加簡潔
比如,Array類的伴生對象的apply方法就實現了接收可變數量的參數,並創建一個Array對象的功能
val a = Array(1, 2, 3, 4, 5)
比如,自己定義的伴生類和伴生對象
object Person {
def apply(name : String) = new Person3(name)
}
class Person(val name : String) {
}
11.12、main方法
就如同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!!!")
}
11.13、用object來實現枚舉功能
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)
Season.withName("spring")
使用枚舉object.values可以遍歷枚舉值
for (ele <- Season.values) println(ele)
11.14、繼承
Scala中,讓子類繼承父類,與Java一樣,也是使用extends關鍵字。繼承就代表,子類可以從父類繼承父類的field和method;然後子類可以在自己內部放入父類所沒有,子類特有的field和method;使用繼承可以有效複用代碼
子類可以覆蓋父類的field和method;但是如果父類用final修飾,field和method用final修飾,則該類是無法被繼承的,field和method是無法被覆蓋的
Scala中,如果子類要覆蓋一個父類中的非抽象方法,則必須使用override關鍵字
override關鍵字可以幫助我們儘早地發現代碼裏的錯誤,比如:override修飾的父類方法的方法名我們拼寫錯了;比如要覆蓋的父類方法的參數我們寫錯了;等等
此外,在子類覆蓋父類方法之後,如果我們在子類中就是要調用父類那就可以使用super關鍵字,顯式地指定要調用父類的方法
Scala中,子類可以覆蓋父類的val field,而且子類的val field還可以覆蓋父類的val field的getter方法;只要在子類中使用override關鍵字即可
class Person {
private var name = "Tom"
val age = 0
def getName = name
}
class Student extends Person {
private var score = "A"
def getScore = score
override val age: Int = 10
override def getName: String = "Hi,I'm " + super.getName
}
11.15、繼承isInstanceOf和asInstanceOf
如果我們創建了子類的對象,但是又將其賦予了父類類型的變量。則在後續的程序中,我們又需要將父類類型的變量轉換為子類類型的變量,應該如何做?
首先,需要使用isInstanceOf判斷對象是否是指定類的對象,如果是的話,則可以使用asInstanceOf將對象轉換為指定類型
注意 :
如果對象是null,則isInstanceOf一定返回false,asInstanceOf一定返回null
如果沒有用isInstanceOf先判斷對象是否為指定類的實例,就直接用asInstanceOf轉換,則可能會拋出異常
class Person
class Student extends Person
val person: Person = new Student
val stu : Student = if (person.isInstanceOf[Student]) person.asInstanceOf[Student]
else null
println(stu.getClass)
結果 :
class com.journey.spark.scala.extend.Student
isInstanceOf只能判斷出對象是否是指定類以及其子類的對象,而不能精確判斷出,對象就是指定類的對象
如果要求精確地判斷對象就是指定類的對象,**那麼就只能使用getClass和classOf了
對象.getClass可以精確獲取對象的類,classOf[類]可以精確獲取類,然後使用==操作符即可判斷**
class Person
class Student extends Person
val person: Person = new Student
person.isInstanceOf[Person] // true
person.getClass == classOf[Person] // false
person.getClass == classOf[Student] //true
11.16 使用模式匹配進行類型判斷
使用模式匹配,功能性上來説,與isInstanceOf一樣,也是判斷主要是該類以及該類的子類的對象即可,不是精準判斷的
class Person
class Student extends Person
val person: Person = new Student
val student: Student = new Student
person match {
case p: Person => println("it's Person's object")
case _ => println("unknown type")
}
不管是 match 之前是person或者student,結果都是打印it's Person's object,其實很簡單,就是都是Person或者Person的子類
11.17 protected
跟java一樣,scala中同樣可以使用protected關鍵字來修飾field和method,這樣在子類中就不需要super關鍵字,直接就可以訪問field和method
還可以使用protected[this],則只能在當前子類對象中訪問父類的field和method,無法通過其他子類對象訪問父類的field和method
class Person {
protected var name: String = "Tom"
protected[this] var hobby: String = "game"
}
class Student extends Person {
def sayHello = println("Hello, " + name)
def makeFriends(student: Student) {
println("my hobby is " + hobby + ", your hobby is " + student.hobby)
}
}
11.18 調用父類的constructor
Scala中,每個類可以有一個主constructor和任意多個輔助constructor,而每個輔助constructor的第一行都必須是調用其他輔助constructor或者是主constructor;因此子類的輔助constructor是一定不可能直接調用父類的constructor的
只能在子類的主constructor中調用父類的constructor,以下這種語法,就是通過子類的主構造函數來調用父類的構造函數
注意:
如果是父類中接收的參數,比如name和age,子類中接收時,就不要用任何val或var來修飾了,否則會認為是子類要覆蓋父類的field
11.19 匿名內部類
匿名子類,也就是説,可以定義一個類的沒有名稱的子類,並直接創建其對象,然後將對象的引用賦予一個變量。之後甚至可以將該匿名子類的對象傳遞給其他函數
class Person(protected val name: String) {
def sayHello = "Hello, I'm " + name
}
val person = new Person("Tom") {
override def sayHello = "Hi, I'm " + name
}
11.20 抽象類
如果在父類中,有某些方法無法立即實現,而需要依賴不同的子來來覆蓋,重寫實現自己不同的方法實現。此時可以將父類中的這些方法不給出具體的實現,只有方法簽名,這種方法就是抽象方法
而一個類中如果有一個抽象方法,那麼類就必須用abstract來聲明為抽象類,此時抽象類是不可以實例化的
在子類中覆蓋抽象類的抽象方法時,不需要使用override關鍵字
如果在父類中,定義了field,但是沒有給出初始值,則此field為抽象field,抽象field意味着,scala會根據自己的規則,為var或val類型的field生成對應的getter和setter方法,但是父類中是沒有該field的
子類必須覆蓋field,以定義自己的具體field,並且覆蓋抽象field,不需要使用override關鍵字
abstract class Person(val name : String) {
val nation : String
def sayHello : Unit
}
class Student(name : String) extends Person(name) {
override def sayHello: Unit = println("Hello " + name)
override val nation: String = "China"
}
12、Trait
12.1、將trait作為接口使用
Scala中的Triat是一種特殊的概念,首先我們可以將Trait作為接口來使用,此時的Triat就與Java中的接口非常類似。在Triat中可以定義抽象方法,就與抽象類中的抽象方法一樣,只要不給出方法的具體實現即可。類可以使用extends關鍵字繼承Trait
注意:
這裏不是implement,而是extends,在scala中沒有implement的概念,無論繼承類還是Trait,統一都是extends。類繼承Trait後,必須實現其中的抽象方法,實現時不需要使用override關鍵字。scala不支持對類進行多繼承,但是支持多重繼承Trait,使用with關鍵字即可
trait PersonTrait {
def eat(food : String)
}
trait MakeFriendsTrait {
def makeFriends(person : Person)
}
class Person (val name : String) extends PersonTrait with MakeFriendsTrait with Cloneable with Serializable {
override def eat(food: String): Unit = {
println(s"person eat ${food}")
}
override def makeFriends(person: Person): Unit = {
println(s"hello my name is ${name}, your name is ${person.name}")
}
}
12.2、在Trait中定義具體方法
Scala中的Triat可以不是隻定義抽象方法,還可以定義具體方法,此時trait更像是包含了通用工具方法的東西。有一個專有的名詞來形容這種情況,就是説Trait的功能混入了類
舉例來説,Trait中可以包含一些很多類都通用的功能方法,比如打印日誌等等,spark中就使用了Trait來定義了通用的日誌打印方法
trait Logger {
def log(message : String) = println(message)
}
class Person (val name : String) extends Logger {
def makeFriends(person: Person): Unit = {
println(s"hello my name is ${name}, your name is ${person.name}")
log(s"makeFriends method is invoked with paramter Person[name=${person.name}]")
}
}
12.3、在Trait中定義具體字段
Scala中的Triat可以定義具體field,此時繼承Trait的類就自動獲得了Trait中定義的field
但是這種獲取field的方式與繼承class是不同的:如果是繼承class獲取的field,實際是定義在父類中的;而繼承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.")
}
12.4 在Trait中定義抽象字段
Scala中的Triat可以定義抽象field,而Trait中的具體方法則可以基於抽象field來編寫,但是繼承Trait的類,則必須覆蓋抽象field,提供具體的值
trait Human {
val msg: String
def say(name: String) = println(msg + ", " + name)
}
class Person(val name : String) extends Human {
override val msg: String = "hello"
def makeFriends(person : Person) {
say(person.name)
println("I'm " + name + ", I want to make friends with you!")
}
}
12.5 Trait混入
有時我們可以在創建類的對象時,指定該對象混入某個Trait,這樣,就只有這個對象混入該trait的方法,而類的其他對象則沒有
trait Logged {
def log(msg : String) {}
}
trait MyLogger extends Logged {
override def log(msg: String): Unit = {
println("log : " + msg)
}
}
class Person(val name : String) extends Logged {
def say = {
println("Hi, I'm " + name )
log("say method is invoked.")
}
}
調用 :
val person1 = new Person("Tom")
person1.say
val person2 = new Person("Tom") with MyLogger
person2.say
顯示 :
Hi, I'm Tom
Hi, I'm Tom
log : say method is invoked.
12.6 Trait調用鏈
Scala中支持讓類繼承多個Trait後,依次調用多個Trait中的同一個方法,只要讓多個Trait的同一個方法中,在最後都執行super.方法即可
類中調用多個Trait中都有的這個方法時,首先會從最右邊的trait的方法開始執行,然後依次往左執行,形成一個調用鏈條
這種特性非常強大,其實就相當於設計模式中的責任鏈模式的一種具體實現依賴
trait Handler {
def handle(data : String) {}
}
trait DataValidHandler extends Handler {
override def handle(data: String): Unit = {
println("check data : " + data)
super.handle(data)
}
}
trait SignatureValidHandler extends Handler {
override def handle(data: String): Unit = {
println("check signature: " + data)
super.handle(data)
}
}
class Person(val name : String) extends SignatureValidHandler with DataValidHandler {
def say = {
println("Hello " + name)
handle(name)
}
}
12.7 混合使用trait的具體方法和抽象方法
在Trait中,可以混合使用具體方法和抽象方法。可以讓具體方法依賴於抽象方法,而抽象方法則放到繼承Trait的類中去實現。這種Trait其實就是設計模式中的模板設計模式的體現
trait Valid {
def getName : String
def valid : Boolean = {
getName == "Tom"
}
}
class Person(val name : String) extends Valid {
override def getName: String = name
}
12.8 Trait的構造機制
在Scala中,Trait也是有構造代碼的,也就是Trait中的,不包含在任何方法中的代碼。而繼承了Trait的類的構造機制如下:
1、父類的構造函數執行
2、trait的構造代碼執行,多個Trait從左到右依次執行
3、構造Trait時會先構造父Trait,如果多個Trait繼承同一個父Trait,則父Trait只會構造一次
4、所有Trait構造完畢之後,子類的構造函數執行
核心 : 構造是從左到右,執行是從右到左
class Person { println("Person constructor!") }
trait Logger { println("Logger constructor!") }
trait MyLogger extends Logger { println("MyLogger constructor!") }
trait TimeLogger extends Logger { println("TimeLogger constructor!") }
class Student extends Person with MyLogger with TimeLogger {
println("Student constructor!")
}
執行結果 :
Logger constructor!
MyLogger constructor!
TimeLogger constructor!
Student constructor!
12.9、Trait繼承class
在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)
}
}
13、函數式編程
Scala是一門既面向對象,又面向過程的語言。因此在Scala中有非常好的面向對象的特性,可以使用Scala來基於面向對象的思想開發大型複雜的系統和工程;而且Scala也面向過程,因此Scala中有函數的概念。在Scala中,函數與類、對象等一樣,都是一等公民。Scala中的函數可以獨立存在,不需要依賴任何類和對象。
Scala的函數式編程,就是Scala面向過程的最好的佐證。也正是因為函數式編程,才讓Scala具備了Java所不具備的更強大的功能和特性
13.1、將函數賦值給變量
Scala中的函數是一等公民,可以獨立定義,獨立存在,而且可以直接將函數作為值賦值給變量。Scala的語法規定,將函數賦值給變量時,必須在函數後面加上空格和下劃線
def main(args: Array[String]): Unit = {
// String => Unit,其實就是輸入是String,無返回
val sayHellFunc: String => Unit = sayHello _
sayHellFunc("Tom")
}
def sayHello(name : String): Unit = {
println("Hello, " + name)
}
13.2、匿名函數
Scala中,函數也可以不需要命名,此時函數被稱為匿名函數。可以直接定義函數之後,將函數賦值給某個變量;也可以將直接定義的匿名函數傳入其他函數之中
Scala定義匿名函數的語法規則就是,(參數名: 參數類型) => 函數體
val sayHelloFunc = (name : String) => println("Hello," + name)
13.3、高階函數
Scala中,由於函數是一等公民,因此可以直接將某個函數傳入其他函數。接收其他函數作為參數的函數,也被稱作高階函數(higher-order function)
def main(args: Array[String]): Unit = {
// 這是一個輸入為String,無返回只有打印的一個函數
val sayHelloFunc = (name : String) => println("Hello, " + name)
// 傳入一個函數和一個String
greeting(sayHelloFunc, "Tom")
}
// 函數的第一個參數是函數,第二個參數是一個String
def greeting(func : (String) => Unit, name : String) {
func(name)
}
輸出 :
Hello, Tom
Array(1, 2, 3, 4, 5, 6).map((num: Int) => num * num)
// 高階函數的另外一個個能是將函數作為返回值
def getGreetingFunc(msg : String) = (name : String) => println(msg + ";" + name)
val greetingFunc : String => Unit = getGreetingFunc("hello")
greetingFunc("Tom")
13.4、高階函數類型推斷
高階函數可以自動推斷出參數類型,而不需要寫明類型;而且對於只有一個參數的函數,還可以省去其小括號;如果僅有的一個參數在右側的函數體內只使用一次,則還可以將接收參數省略,並且將參數用_來替代
def greeting(func : (String) => Unit, name : String) {
func(name)
}
greeting((name : String) => println("Hello," + name), "Tom")
greeting((name) => println("Hello," + name), "Tom")
greeting(name => println("Hello," + name), "Tom")
def triple(func: (Int) => Int) = { func(3) }
triple(3 * _)
13.5、Scala的常用高階函數
// 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(_ < _)
13.6、閉包
函數在變量不處於其有效作用域時,還能夠對變量進行訪問,即為閉包
def getGreetingFunc(msg : String) = (name : String) => println(msg + "," + name)
val greetingFunc = getGreetingFunc("hello")
greetingFunc("Tom")
兩次調用getGreetingFunc函數,傳入不同的msg,並創建不同的函數返回。然而,msg只是一個局部變量,卻在getGreetingFunc執行完之後,還可以繼續存在創建的函數之中;greetingFunc("leo"),調用時,值為"hello"的msg被保留在了函數體內部,可以反覆的使用,這種變量超出了其作用域,還可以使用的情況,即為閉包
Scala通過為每個函數創建對象來實現閉包,實際上對於getGreetingFunc函數創建的函數,msg是作為函數對象的變量存在的,因此每個函數才可以擁有不同的msg
Scala編譯器會確保上述閉包機制
13.7、SAM轉換
在Java中,不支持直接將函數傳入一個方法作為參數,通常來説,唯一的辦法就是定義一個實現了某個接口的類的實例對象,該對象只有一個方法;而這些接口都只有單個的抽象方法,也就是single abstract method,簡稱為SAM
由於Scala是可以調用Java的代碼的,因此當我們調用Java的某個方法時,可能就不得不創建SAM傳遞給方法,非常麻煩;但是Scala又是支持直接傳遞函數的。此時就可以使用Scala提供的,在調用Java方法時,使用的功能,SAM轉換,即將SAM轉換為Scala函數
要使用SAM轉換,需要使用Scala提供的特性,隱式轉換
import javax.swing._
import java.awt.event._
val button = new JButton("Click")
button.addActionListener(new ActionListener {
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!!!"))
13.8、Currying函數
Curring函數,指的是,將原來接收兩個參數的一個函數,轉換為兩個函數,第一個函數接收原先的第一個參數,然後返回接收原先第二個參數的第二個函數
在函數調用的過程中,就變為了兩個函數連續調用的形式
def sum(a : Int, b : Int) = a + b
def sum2(a : Int) = (b : Int) => a + b
def sum3(a :Int)(b : Int) = a + b
14、集合
Scala中的集合體系主要包括:Iterable、Seq、Set、Map。其中Iterable是所有集合根Trait
Scala中的集合是分成可變和不可變兩類集合的,其中可變集合就是説,集合的元素可以動態修改,而不可變集合的元素在初始化之後,就無法修改了。分別對應scala.collection.mutable和scala.collection.immutable兩個包
Seq下包含了Range、ArrayBuffer、List等子trait
其中Range就代表了一個序列,通常可以使用“1 to 10”這種語法來產生一個Range ArrayBuffer就類似於Java中的ArrayList
List代表一個不可變的列表
14.1、List
List有head和tail,head代表List的第一個元素,tail代表第一個元素之後的所有元素,list.head,list.tail
List有特殊的::操作符,可以用於將head和tail合併成一個List,0 :: list
如果一個List只有一個元素,那麼它的head就是這個元素,它的tail是Nil
def decorator(list : List[Int], prefix : String) {
if (list != Nil) {
println(prefix + list.head)
decorator(list.tail, prefix)
}
}
14.2、Nil/null/None/noting
Nil : 在Scal中,Nil是一個空列表,它是List[Noting]類型的一個實例,它表示一個空的集合或列表
null : null是Java中的一個關鍵字,表示一個空引用。在Scal中,null是一個特殊的類型,它可以被賦值給任何引用類型的變量
None : none是Option類型的一個實例,它表示一個空的Option。Option類型是Scala中的一個容器類型,它可以包含一個值或者空值
nothing : noghting是Scala中的一個特殊類型,它表示一個不存在的值。它通常用於標識一個函數永遠不會反悔值,或者表示一個異常的類型
val map = Map("Japan" -> "Tokyo", "China" -> "Beijing")
map.get("France") match {
case Some(provice) => println(provice)
case None => println("unknow")
}
// val nameMaybe: Option[String] = None
val nameMaybe: Option[String] = Some("Tom")
nameMaybe match {
case Some(_) => println("yes")
case None => println("No name")
}
14.3、LinkedList
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
}
14.4、Set
// Set代表一個沒有重複元素的集合
// 將重複元素加入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")
15、模式匹配
15.1、簡單模式匹配
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")
}
}
15.2、在模式匹配中使用if守衞
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 == "Tom" => println(name + ", you are a good boy, come on")
case _ => println("you need to work harder")
}
}
15.3、在模式匹配中進行變量賦值
Scala的模式匹配語法,有一個特點在於,可以將模式匹配的默認情況,下劃線,替換為一個變量名,此時模式匹配語法就會將要匹配的值賦值給這個變量,從而可以在後面的處理語句中使用要匹配的值
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 == "Tom" => println(name + ", you are a good boy, come on, your grade is " + _grade)
case _grade => println("you need to work harder, your grade is " + _grade)
}
}
15.4、對類型進行模式匹配
Scala的模式匹配一個強大之處就在於,可以直接匹配類型,而不是值
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!" )
}
}
15.5、對Array和List進行模式匹配
對Array進行模式匹配,分別可以匹配帶有指定元素的數組、帶有指定個數元素的數組、以某元素打頭的數組
對List進行模式匹配,與Array類似,但是需要使用List特有的::操作符
def greeting(arr: Array[String]) {
arr match {
case Array("Tom") => println("Hi, Tom!")
case Array(girl1, girl2, girl3) => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
case Array("Tom", _*) => println("Hi, Leo, please introduce your friends to me.")
case _ => println("hey, who are you?")
}
}
def greeting(list: List[String]) {
list match {
case "Tom" :: Nil => println("Hi, Tom!")
case girl1 :: girl2 :: girl3 :: Nil => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
case "Tom" :: tail => println("Hi, Tom, please introduce your friends to me.")
case _ => println("hey, who are you?")
}
}
15.6、case class與模式匹配
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(person: Person) {
person 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!")
}
}
15.7、Option與模式匹配
Scala有一種特殊的類型,叫做Option。Option有兩種值,一種是Some,表示有值,一種是None,表示沒有值。
Option通常會用於模式匹配中,用於判斷某個變量是有值還是沒有值,這比null來的更加簡潔明瞭
val grades = Map("Leo" -> "A", "Jack" -> "B", "Jen" -> "C")
def getGrade(name: String) {
val grade = grades.get(name)
grade match {
case Some(grade) => println("your grade is " + grade)
case None => println("Sorry, your grade information is not in the system")
}
}
16、類型參數
16.1、泛型類
泛型類,顧名思義,其實就是在類的聲明中,定義一些泛型類型,然後在類內部,比如field或者method,就可以使用這些泛型類型
使用泛型類,通常是需要對類中的某些成員,比如某些field和method中的參數或變量,進行統一的類型限制,這樣可以保證程序更好的健壯性和穩定性
如果不使用泛型進行統一的類型限制,那麼在後期程序運行過程中,難免會出現問題,比如傳入了不希望的類型,導致程序出問題。
在使用類的時候,比如創建類的對象,將類型參數替換為實際的類型,即可
Scala自動推斷泛型類型特性:直接給使用了泛型類型的field賦值時,Scala會自動進行類型推斷
class Student[T](val schoolId: T,val stuId: T) {
def getIdentify= "S-" + schoolId + "-" + stuId
}
val student = new Student[Int](11, 101)
student.getIdentify
16.2、泛型函數
泛型函數,與泛型類類似,可以給某個函數在聲明時指定泛型類型,然後在函數體內,多個變量或者返回值之間,就可以使用泛型類型進行聲明,從而對某個特殊的變量,或者多個變量,進行強制性的類型限制
與泛型類一樣,你可以通過給使用了泛型類型的變量傳遞值來讓Scala自動推斷泛型的實際類型,也可以在調用函數時,手動指定泛型類型
def getCard[T](content : T) = {
content match {
case e : Int => "card: 001, " + e
case e : String => "card: this is your card, " + e
case _ => "card: " + content
}
}
getCard[String]("xxx")
16.3、上邊界Bounds
在指定泛型類型的時候,有時,我們需要對泛型類型的範圍進行界定,而不是可以是任意的類型。 比如,我們可能要求某個泛型類型,它就必須是某個類的子類,這樣在程序中就可以放心地調用泛型類型繼承的父類的方法,程序才能正常的使用和運行。此時就上邊界Bounds
Scala的上下邊界特性允許泛型類型必須是某個類的子類,或者必須是某個類的父類
class Person(val name: String) {
def sayHello = println("Hello, I'm " + name)
def makeFriends(person: Person) {
sayHello
person.sayHello
}
}
class Student(name: String) extends Person(name)
class Party[T <: Person](p1: T, p2: T) {
def play = p1.makeFriends(p2)
}
val stu1 = new Student("zhangsan")
val stu2 = new Student("lisi")
val party = new Party[Student](stu1, stu2)
party.play
注意 : T <: Person 是可以是Person或者Person的子類
16.4、下邊界Bounds
除了指定泛型類型的上邊界,還可以指定下邊界,即指定泛型類型必須是某個類的父類
def getIDCard[R >: Child](person: R) {
person match {
case _: Child => println("please tell us your parents' names.")
case _: Father => println("sign your name for your child's id card.")
case _ => println("sorry, you are not allowed to get id card.")
}
}
class Father(val name: String)
class Child(name: String) extends Father(name)
注意 : R >: Child 是可以是Child的父類和自己的
16.5、View Bounds
上下邊界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(dog: Object): Person = if(dog.isInstanceOf[Dog]) {val _dog = dog.asInstanceOf[Dog]; new Person(_dog.name) } else Nil
class Party[T <% Person](p1: T, p2: T)
16.6、Context Bounds
Context Bounds是一種特殊的Bounds,它會根據泛型類型的聲明,比如“T:類型”要求必須存在一個類型為“類型[T]”的隱式值。Context Bounds之所以叫Context,是因為它基於的是一種全局的上下文,需要使用到上下文中的隱式值以及注入
class Calculator[T: Ordering] (val number1: T, val number2: T) {
def max(implicit order: Ordering[T]) = if(order.compare(number1, number2) > 0) number1 else number2
}
16.7、Manifest Context Bounds
在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*) = {
val foodPackage = new Array[T](food.length)
for(i <- 0 until food.length) foodPackage(i) = food(i)
foodPackage
}
16.8、協變和逆變
Scala的協變和逆變是非常有特色的!完全解決了Java中的泛型的一大缺憾!
舉例來説,Java中,如果有Professional是Master的子類,那麼Card[Professionnal]是不是Card[Master]的子類?答案是:不是。因此對於開發程序造成了很多的麻煩。
而Scala中,只要靈活使用協變和逆變,就可以解決Java泛型的問題
class Master
class Professional extends Master
// 大師以及大師級別以下的名片都可以進入會場
class Card[+T] (val name: String)
def enterMeet(card: Card[Master]) {
println("welcome to have this meeting!")
}
// 只要專家級別的名片就可以進入會場,如果大師級別的過來了,當然可以了!
class Card[-T] (val name: String)
def enterMeet(card: Card[Professional]) {
println("welcome to have this meeting!")
}
16.9、Existential Type
在Scala裏,有一種特殊的類型參數,就是Existential Type,存在性類型
Array[T] forSome { type T }
Array[_] 表示某一種存在的類型,其實就是一個佔位符
17、隱式轉換
17.1、隱式轉換基本原理
要實現隱式轉換,只要程序可見的範圍內定義隱式轉換函數即可。Scala會自動使用隱式轉換函數。隱式轉換函數與普通函數唯一的語法區別就是,要以implicit開頭,而且最好要定義函數返回類型
class SpecialPerson(val name: String)
class Student(val name: String)
class Older(val name: String)
// 其實就是可以將Student、Older轉換為SpecialPerson
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 null
}
var ticketNumber = 0
def buySpecialTicket(p: SpecialPerson) = {
ticketNumber += 1
"T-" + ticketNumber
}
調用 :
val stu = new Student("Student")
println(buySpecialTicket(stu))
val older = new Student("Older")
println(buySpecialTicket(older))
val specialPerson = new SpecialPerson("SpecialPerson")
println(buySpecialTicket(specialPerson))
17.2、使用隱式轉換加強現有類型
隱式轉換非常強大的一個功能,就是可以在不知不覺中加強現有類型的功能。也就是説,可以為某個類定義一個加強版的類,並定義互相之間的隱式轉換,從而讓源類在使用加強版的方法時,由Scala自動進行隱式轉換為加強類,然後再調用該方法
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 tom = new Man("Tom")
tom.emitLaser
17.3、隱式轉換函數作用域與導入
Scala默認會使用兩種隱式轉換,一種是源類型,或者目標類型的伴生對象內的隱式轉換函數;一種是當前程序作用域內的可以用唯一標識符表示的隱式轉換函數
如果隱式轉換函數不在上述兩種情況下的話,那麼就必須手動使用import語法引入某個包下的隱式轉換函數,比如import test._。通常建議,僅僅在需要進行隱式轉換的地方,比如某個函數或者方法內,用import導入隱式轉換函數,這樣可以縮小隱式轉換函數的作用域,避免不需要的隱式轉換
17.4、隱式參數
所謂的隱式參數,指的是在函數或者方法中,定義一個用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.")
}
調用 :
val signPen = new SignPen
signForExam("xxx")(signPen)
如感興趣,點贊加關注,謝謝!!!