1、伴生對象
形如:
有一個類
class Test{
}
一個object和該類同名
object Test{
}
object Test的作用類似于靜態類(工具類),其中的所有成員都是靜態的,在object Test中可以直接訪問class Test的成員;反之,class Test中要通過object Test來引用其成員例如使用Test.的方式
2、apply方法
class中的apply:是一個比較特殊的方法,通過這個class new 出來的對象,可以直接通過對象(),這樣的方式來調用apply方法
object中的apply:比較常用,例如,通常使用數組時是下面這樣的代碼:
val arr = new Array[Int](3)
arr(0) = 1
arr(1) = 2
arr(2) = 3
但是,在scala中可以通過伴生對象的apply方法,我們可以很方便的構建類的對象,而不必知道和操作這個過程,如下:
val arr = Array(1,2,3)
通過伴生對象的apply方法,我們可以很方便的構建類的對象,而不必知道和操作這個過程,在apply方法中其實也是new出一個Array,并在其中做好了初始化操作
源代碼如下:
/** Creates an array of `Int` objects */// Subject to a compiler optimization in Cleanup, see above.def apply(x: Int, xs: Int*): Array[Int] = {val array = new Array[Int](xs.length + 1)array(0) = xvar i = 1for (x <- xs.iterator) { array(i) = x; i += 1 }array}
3、繼承
沒啥好說的,概念和其他語言一樣,只是語法有點區別:
class Father(name:String){
...
}
//構造子類的時候會先構造父類,所以要將父類構造函數需要的參數給它
class Son(name:String,age:Int) extends Father(name){
...
}
4、trait特質
trait可以當接口來用,但是和其他語言的接口有些不同,trait里面竟然還可以有方法的定義!
那么這樣和抽象類不是一樣的了,干嘛還要trait?
Scala中也是單繼承,也就是說一個類只能繼承一個父類,需要有很多子類的特性的時候就可以通過繼承多個trait來實現(可以把抽象類看成是一個統一的模板,trait則是其他七七八八的裝飾,可加可減靈活性高)
在Scala中如果一個class,或者一個trait直接繼承了另外一個trait,那么語法是一樣的:
trait Test{
...
}class Test1 extends Test{
...
}
當多重繼承時,trait要使用with關鍵字,構造順序從左往右,且不重復構造:
class Human{...}
trait ITeacher extends Human{...}
trait IBasketballPlayer extends Human{...}
class BasketballTeacher extends Human with ITeacher with IBasketballPlayer{...}
由于ITeacher和IBasketballPlayer都繼承了Human,理論上在構造他們的時候會去構造他們的父類,也就是Human,但是由于Human之間在構造BasketballTeacher 的時候已經構造過了,所以這里不再重復構造
上面代碼演示的是在定義class的時候混入trait
實際上也可以在new object的時候使用,這樣可以在具體的場景中定義具體處理的對象(和定義class的時候直接混入的區別是,前者new出來的所有object都帶有trait特質的,后者只作用在一個單一的object):
class Human{...}
trait ITeacher extends Human{...}
trait IBasketballPlayer extends Human{...}val t1 = new Human with ITeacher with IBasketballPlayer{
//如果有抽象方法,在此重寫
}
需要注意的是,混入的trait必須都繼承自同一個父類
trait的AOP實現:
trait DoSomething {def work
}class Do extends DoSomething{override def work: Unit = {println("working!")}
}trait BeforeAndAfter extends DoSomething {abstract override def work: Unit = {println("init...")super.workprintln("destroy...")}
}object test {def main(args: Array[String]) {val worker = new Do with BeforeAndAfterworker.work}
}
上面的代碼中,使用trait實現了一個簡單的AOP編程實例
首先定義了一個trait DoSomething,里面有一個抽象方法work
class Do繼承自DoSomething,并實現了具體的work方法
這時new一個Do的object之后調用work應該打印出一行記錄
之后定義了另外一個trait BeforeAndAfter,也繼承了DoSomething并重寫work
在其重寫的方法中將父trait的work方法放在初始化和銷毀的操作之間,由于父trait的work方法是抽象的,此時又調用了這個抽象方法,所以這個重寫的work仍然是抽象的,要加上abstract關鍵字
在new Do的object的時候混入這個trait就可以實現AOP,代碼執行過程應該是這樣的:
1、調用worker的work方法,由于混入了trait,所以實際調用的是這個trait里面的work
2、這個trait里面的work先進行了初始化操作,然后調用父trait的work,而這個方法的具體實現是在Do類中完成的,所以又調用了Do類中的具體work實現
3、work調用完成之后,進行銷毀操作
5、包對象
包對象的定義及作用如下:
//這是一個包對象
package obejct Person{...}
//這是一個包的定義
package Person{
//此時,在這個包的作用于范圍之內,可以直接訪問包對象的成員
}//使用這種語法的import意思是將scala包中的StringBuilder隱藏起來不使用(使用別的包的StringBuilder)
import scala.{StringBuilder => _}
6、文件操作
//讀取本地文件
val localFile = Source.fromFile("file path")
val lines = localFile.getLines
//讀取網絡文件
val networkFile = Source.fromURL("file url")
//創建一個寫入器
val writer = new PrintWriter(new File("file path"))
writer.println("something to write")
writer.close
//控制臺讀取
Console.readLine
7、正則表達式
//可以直接使用字符串.r的方式返回一個Regex對象
val regex1 = "[0-9]+".r
//三個引號表示表達式中的符號都是原意,而不是轉義符(如\)
val regex2 = """\s+[0-9]+\s+""".r
//Regex對象可以直接調用findAllIn/findFirstIn等方法
regex1.findAllIn("1234 dqd qwdq")//該方法會返回全部匹配項
正則表達式和模式匹配相結合
val regex = """([0-9]+) ([a-z]+)""".r
val line = "123 abc"
line match {
//line如果符合regex1規則,會將其自動匹配成(num,str)格式
case regex(num, str) => println(num + ":" + str)
case _ => println("~~~~~~~~~`")
}
8、內部函數的定義
開發過程中,經常將各個子功能定義為一個個函數,在通過一個統一入口函數在調用這個子函數
但是這樣存在一個問題,入口函數和子函數通常是定義在一起的,既然入口函數可以被外部調用,那么子函數同樣也可以
這就不是我們想要的效果了,我們需要實現的是外部只能調用入口函數,而對于其他的子功能都是不可見的
這也就是高內聚低耦合的思想
在Scala中,函數是一等公民,意味著函數可以當做變量成員來使用
那么,在函數中可以定義變量,也就可以定義函數
這就是內部函數
def portal{//內部函數定義def action1{}def action2{}...調用內部函數action1action2
}
9、閉包
簡單的說,閉包就是指在一個函數中,能夠訪問另外一個函數的變量(必須要訪問這個變量才能完成函數的工作),讀取這個變量之后,這個函數就關閉執行,成為閉包
一個簡單的閉包例子:
//這個函數中,要完成功能必須要知道more的值
def add(more:Int) = (x:Int) => x + more
//傳入more的值為1,返回值其實還是一個函數x+1
val a = add(9)
val b = add(90)
//調用這個返回的函數,傳入x=1
a(1)
b(10)
10、高階函數
高階函數簡單的說就是 參數是函數 的函數
例如map、reduce等需要傳入匿名函數的函數
具體操作就不詳細說明了,因為高階函數太多了= =使用方法都是差不多的
11、SAM轉換
即Simple Abstract Method
在例如Java等語言中,代碼通常是這樣子寫的:
jButton.addActionListener(new ActionListener{override def actionPerformed(event:ActionEvent){counter += 1}
})
jButton的addActionListener需要一個格式為(event:ActionEvent)=>Unit的方法,上面的代碼中直接new出了一個ActionListener并重寫其方法傳入
這叫做樣本代碼,即符合這個樣本格式的方法才能使用
而很多時候,addActionListener這類的方法并不需要知道這么多信息,它只需要我們給它一個方法就行了,而不是一大堆的重新new對象,重寫方法
在Scala中是這么解決問題的:
//這是一個隱式轉換,其實起到的作用就是樣本代碼,名字任意,只要參數和返回值是符合固定格式的即可
implicit def makeAction(action:(ActionEvent) => Unit) = {new ActionListener{override def actionPerformed(event:ActionEvent){action(event)}
}//有了上面的隱式轉換,我們就可以很簡潔的直接將方法當做參數傳入
jButton.addActionListener((event:ActionEvent) => counter += 1)
此時,在這個界面的所有地方,都可以傳入(ActionEvent) => Unit類型的函數給需要的函數了
總結SAM轉換:
借助隱式轉換,將樣本代碼省去
傳入一個方法,自動掃描當前區域的隱式轉換(定義了樣本代碼),如果轉換成功就可以調用
12、Currying函數柯里化
函數的柯里化即將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。
簡單的例子:
//多參數的函數
def add(x:Int,y:Int) = x + y
add(1,2)
//接受一個參數的函數
def add(x:Int) = (y:Int) => x + y
add(1)(2)
對于只接受一個參數的函數格式是不是挺熟悉的?在之前的閉包中使用的就是這種格式的函數
Scala中支持定義簡介的柯里化函數
def add(x:Int)(y:Int) = x + y
柯里化的作用有很多,其中一個就是用來做類型推斷
在一些場合,Scala編譯器可以通過第一個參數的類型推薦第二個參數的類型以便進行一些操作
13、模式匹配
Scala的模式匹配類似于switch,但是更加靈活,格式如下:
val line = ...
line match{case ... => ......case _ => ...
}
每個case都有返回值(沒有特殊指定的話)
case分支不用break
case后可以用常亮,變量,表達式,方法等
模式匹配還有很多其他的特殊的用法
例如:匹配type,array、list、tuple格式
def match_type(t:Any) = t match{case i:Int => println("Int")case s:String => println("String")case m:Map[_,_] => m.foreach(println)
}def match_array(arr:Any) = arr match{//數組中只有一個0的匹配成功case Array(0) => ...//數組中有任意的兩個數匹配成功case Array(x,y) => ...//數組中第一個為0,還有任意多個元素的匹配成功case Array(0,_*) => ...
}def match_list(lst:Any) = lst match{//List中有一個0的匹配成功(Nil表示一個空的List,::運算符是從右至左運算的,表示空List加上一個0元素,即為只有一個0的List集合)case 0 :: Nil => ...//List中有任意的兩個數匹配成功case x :: y :: Nil => ...//集合中第一個為0,還有任意多個元素的匹配成功(tail表示List中除了第一個元素外的所有元素)case 0 :: tail => ...
}
14、樣例類和樣例對象
通常在系統開發中,我們需要自己定義一些數據結構來進行統一的消息傳遞,例如經常使用的Model
在Scala中,使用樣例類和樣例對象來完成這個功能
定義的格式如下:
case class Person(...)
case object Person1(...)
15、嵌套的樣例類和模式匹配結合
abstract class Item{...}
case class Book(name:String,price:Int) extends Item
//BookList中嵌套了一個Book的樣例類
case class BookList(num:Int,books:Book*) extends Itemdef match_caseclass(item:Item) = item match{//匹配BookList,用占位符_代替的屬性都不關心,只要Book的name屬性case BookList(_,Book(name,_),_*) => ...//可以使用別名@Book的方式進行模式匹配,后面的方法體中可以使用別名直接操作case BookList(_,b1 @ Book(_,_),other @ _*) => ...
}
未完待續…