異常處理
- 類似Java,使用
try..catch
捕獲處理異常 - 不同的是catch中使用模式匹配的思想
try {
// 代碼
}
catch {
case ex:異常類型1 => // 代碼
case ex:異常類型2 => // 代碼
}
finally { // 不管是否出現異常都會執行
// 代碼
}
- 同樣,可以使用
throw
拋出異常
提取器
- 是一個帶有
unapply
方法的對象 - 從傳遞給它的對象中提取出構造該對象的參數
- 類似於apply()方法,需要在伴生對象中使用
class Student {
var name:String = _ // 姓名
var age:Int = _ // 年齡
// 實現一個輔助構造器
def this(name:String, age:Int) = {
this() // 父類構造方法
this.name = name
this.age = age
}
}
object Student { // 伴生對象
// 構造器
def apply(name:String, age:Int): Student = new Student(name, age)
// 解構器
def unapply(arg: Student): Option[(String, Int)] = Some(arg.name, arg.age))
}
object extractor_DEMO {
def main(args: Array[String]): Unit = {
val zhangsan = Student("張三", 20) // 使用伴生對象的apply方法直接實例化
zhangsan match { // 自動實現實例類的解構方法
case Student(name, age) => println(s"姓名:$name 年齡:$age")
case _ => println("未匹配")
}
}
}
泛型
- 泛型用於指定方法或類可以接受任意類型參數,參數在實際使用時才被確定
- 使用泛型可以使得類或方法具有更強的通用性,泛型無處不在
- 使用方括號
[ ]
來定義類型參數(注意區分類型與類型參數)
def getMiddle[A](arr:Array[A]) = arr(arr.length / 2)
def main(args: Array[String]): Unit = {
val arr1 = Array(1,2,3,4,5)
val arr2 = Array("a", "b", "c", "d", "f")
println(getMiddle[Int](arr1)) // 傳參時確定類型
println(getMiddle[String](arr2))
// 簡寫方式
println(getMiddle(arr1))
println(getMiddle(arr2))
}
- 泛型類
// 類名後面的方括號,就表示這個類可以使用兩個類型、分別是T和S
// 這個名字可以任意取,可以叫W或者C
class Pair[T, S](val first: T, val second: S)
case class People(var name:String, val age:Int)
object Pair {
def main(args: Array[String]): Unit = {
val p1 = new Pair[String, Int]("張三", 10)
val p2 = new Pair[String, String]("張三", "1988-02-19")
val p3 = new Pair[People, People](People("張三", 20), People("李四", 30))
}
}
- 隨着泛型還有一些其他概念:
上下界
- 在指定泛型類型時,有時需要界定泛型類型的範圍,而不是接收任意類型
- 上下邊界特性允許泛型類型是某個類的子類,或者是某個類的父類
//要控制Person只能和Person、Policeman聊天,但是不能和Superman聊天。此時,還需要給泛型添加一個下界。
//上下界
class Pair[T <: Person, S >: Policeman <:Person](val first: T, val second: S) {
def chat(msg:String) = println(s"${first.name}對${second.name}說: $msg")
}
class Person(var name:String, val age:Int)
class Policeman(name:String, age:Int) extends Person(name, age)
class Superman(name:String) extends Policeman(name, -1)
object Pair {
def main(args: Array[String]): Unit = {
//運行錯誤:第二個參數必須是Person的子類(包括本身)、Policeman的父類(包括本身)
val p3 = new Pair[Person,Superman](new Person("張三", 20), new Superman("李四"))
p3.chat("你好!")
}
}
協變
class Pair[+T]
,這種情況是協變。類型B是A的子類型,那麼Pair[B]可以認爲是Pair[A]的子類型- 這種情況,類型參數的方向和類型的方向是一致的
class Pair[+T](a:T)
object Pair {
def main(args: Array[String]): Unit = {
val p1 = new Pair("hello")
// String是AnyRef的子類
val p2:Pair[AnyRef] = p1
println(p2)
}
}
逆變
class Pair[-T]
,這種情況是逆變。類型B是A的子類型,Pair[A]反過來可以認爲是Pair[B]的子類型- 這種情況,類型參數的方向和類型的方向是相反的
非變
class Pair[T]
,這種情況就是非變(默認),類型B是A的子類型,Pair[A]和Pair[B]沒有任何從屬關係
class Super
class Sub extends Super
//非變
class Temp1[A](title: String)
//協變
class Temp2[+A](title: String)
//逆變
class Temp3[-A](title: String)
object Covariance_demo {
def main(args: Array[String]): Unit = {
val a = new Sub()
// 與Java同樣,向上轉型,不屬於泛型
val b:Super = a
// 非變
val t1:Temp1[Sub] = new Temp1[Sub]("測試")
// 報錯!默認不允許轉換
// val t2:Temp1[Super] = t1
// 協變
val t3:Temp2[Sub] = new Temp2[Sub]("測試")
val t4:Temp2[Super] = t3 // 類似Java普通類向上轉型
// 逆變
val t5:Temp3[Super] = new Temp3[Super]("測試")
val t6:Temp3[Sub] = t5 // 類似Java普通類向下轉型
}
}
隱式轉換和隱式參數
- 可以允許你手動指定,將某種類型的對象轉換成其他類型的對象或者是給一個類增加方法
- 隱式轉換:使用
implicit
修飾的方法實現把一個原始類轉換成目標類,進而可以調用目標類中的方法 - 所有的隱式轉換和隱式參數必須定義在一個object中
//todo:一個類隱式轉換成具有相同方法的多個類
class C
class A(c:C) {
def readBook(): Unit ={
println("A說:好書好書...")
}
}
class B(c:C){
def readBook(): Unit ={
println("B說:看不懂...")
}
def writeBook(): Unit ={
println("B說:不會寫...")
}
}
object AB{
// 將原始類C轉換爲目標類A B
// 此時的C就有了A/B的能力
implicit def C2A(c:C) = new A(c)
implicit def C2B(c:C) = new B(c)
}
object B{
def main(args: Array[String]) {
//導包
//1. import AB._ 會將AB類下的所有隱式轉換導進來
//2. import AB.C2A 只導入C類到A類的的隱式轉換方法
//3. import AB.C2B 只導入C類到B類的的隱式轉換方法
import AB._
val c=new C
//由於A類與B類中都有readBook(),只能導入其中一個,否則會衝突
//c.readBook()
//C類可以執行B類中的writeBook() 即C2B
c.writeBook()
}
}
- 隱式參數:在函數或者方法中,定義一個用implicit修飾的參數,Scala會嘗試找到一個指定類型的用implicit修飾的參數,即隱式值,傳入方法
- Scala會在兩個範圍內查找:一種是當前作用域內可見的val或var定義的隱式變量(導包);一種是隱式參數類型的伴生對象內的隱式值
//todo:隱式參數案例:員工領取薪水
object Company{
// 在object中定義用implicit修飾的參數(隱式參數)
// 注意:同一類型的隱式值只允許出現一次,否則會報錯
implicit val xxx="zhangsan"
implicit val yyy=10000.00
//implicit val zzz="lisi"
}
class Boss {
// 定義一個用implicit修飾的參數,類型爲String
// 注意參數匹配的類型 它需要的是String類型的隱式值
def callName(implicit name:String):String={
name+" is coming !"
}
// 定義一個用implicit修飾的參數,類型爲Double
// 注意參數匹配的類型 它需要的是Double類型的隱式值
def getMoney(implicit money:Double):String={
" 當月薪水:"+money
}
}
object Boss extends App{
//使用import導入定義好的隱式值,注意:必須先加載否則會報錯
import Company.xxx // String類型的隱式值
import Company.yyy // Int類型的隱式值
val boss = new Boss
// Scala會嘗試找到一個指定類型的用implicit修飾的參數,即隱式值,傳入方法
println(boss.callName+boss.getMoney)
}
結語
以上四篇基本介紹了在大數據開發過程中經常使用到的基本Scala語法,這並不是全面的語法總結。Scala是一門多範式編程語言,有很強的靈活性,掌握思想比較關鍵,後面會通過案例和源碼的方式呈現基本原理。