操作符會涉及到以下部分的內容:
1.實現自己的操作符;
2.隱式轉換(自動被應用的類型轉換);
3.apply、udpate和unapplay這些特殊方法。
1.中置操作符:
Scala中包含下邊的表達式:
a 標識符 b
其中的標識符代表一個帶有兩個參數的方法(一個隱式的參數和一個顯示的參數)。
例如:1 to 10 實際上是 1.to(10)。
這樣的表達式叫做中置表達式,因爲操作符位於兩個參數之間。如果在自己的類中定義操作符,需要以你想要用作操作符的名稱來定義一個方法。
下面定義的這個類,自定義了一個對象的操作符:
/** * 定義兩個對象相乘的操作符,來實現如下公式:(n1/d1) * (n2/d2) = (n1*n1) / (d1*d2) * Infixtest包含2個成員num和dev, 定義乘號(*) 即:Infixtest的2個對象(infix1和infix2)相乘 , * 也就是: Infix1 * Infix2 = (infix1.n1 * infix2.n2)/(infix1.d1 * infix2.d2) */ class Infixtest(val num:Double , val dev:Double){ def *(other:Infixtest) = { (num * other.num)/(dev*other.dev) } }
測試語句:
def main(args:Array[String]) { val infix1 = new Infixtest(2.1,2) val infix2 = new Infixtest(10.2,4) println("兩個對象的乘積:" + infix1.*(infix2)) }
這樣我們就自定義了*(當然可以選擇任何你喜歡的操作符,如#*等)實現了對象的相乘。
2.一元操作符:
只有一個參數的操作符稱爲一元操作符。如果操作符出現在參數後,那麼就是一個後置操作符(即: a 標識符 )。相反則是前置操作符(即:標識符 a)。
後置操作符:
1 toString 等同於 1.toString()
前置操作符:
+ - ! ~ 可以作爲前置操作符。它們被轉換成對名爲unary_操作符的方法調用。
-a 等同於 a.unary_-
println(1.unary_-)
3.優先級:
在有多個操作符同時出現,但是又沒有括號時,判斷操作符的優先級就十分重要了。
Scala可以隨意定義操作符,因此需要用一套優先級判定方案,對所有操作符生效,但同時保留人們所熟悉的標準操作符的優先順序。
除了賦值操作符外(a += 2 ),優先級由操作符的首字符決定。
後置操作符優先級低於中置操作符:
a 中置操作符 b 後置操作符 等同於: (a 中置操作符 b) 後置操作符
當有一系列同優先級的操作符時,操作符的結合性決定了它們是從左到右還是從右到左求值。絕大部分操作符都是左結合的(例如,12+8+21 等同於 (12+8)+21),除了以下兩種情況:
以冒號(:)結尾的操作符;
賦值操作符
4.apply和update方法:
函數或者方法可以如下邊方式調用:
f(arg1,arg2,...)
如果f不會函數或者方法,那麼這個表達式就等同於調用apply方法:
f.apply(arg1,arg2,...)
但是如果它出現在賦值語句的等號的左側,即:f(arg1,arg2,...) = value,則等同於調用update方法:
f.update(arg1,arg2,...)
下面是一個使用apply和update方法的實例:
/** * apply方法和update方法: */ import scala.collection.mutable.HashMap val hashtest = new HashMap[String,Int] //f(arg1,arg2,...)在賦值語句等號的左側,相當於調用 f.update。 //下邊語句相當於 hashtest.update("Bob",131) hashtest("Bob") = 131 //相當於hashtest.apply("Bob") val bobhash = hashtest("Bob")
apply方法也經常被用在伴生對象,用來構造對象而不用顯式地使用new。
5.提取器:
所謂提取器就是一個帶有unapply方法的對象。unapply方法是伴生對象中apply方法的反向操作。
apply方法接收構造參數,然後將其變爲對象。而unapply方法接收一個對象,然後從中提取值——通常這些值就是當初用來構造該對象的值。
通常而言,模式匹配可能會失敗。因此unapply方法返回的是一個option。它包含一個元組,每個匹配到的變量各有一個值與之對應。
下面使用上邊的Infixtest類的例子,首先定義伴生對象(需要定義unapply方法):
/** * 定義兩個對象相乘的操作符,來實現如下公式:(n1/d1) * (n2/d2) = (n1*n1) / (d1*d2) * Infixtest包含2個成員num和dev, 定義乘號(*) 即:Infixtest的2個對象(infix1和infix2)相乘 , * 也就是: Infix1 * Infix2 = (infix1.n1 * infix2.n2)/(infix1.d1 * infix2.d2) */ class Infixtest(val num:Double , val dev:Double){ def *(other:Infixtest):Infixtest = { Infixtest(num * other.num,dev*other.dev) } } //apply伴生對象 object Infixtest{ //apply方法接收2個參數來創建對象。 可以省去原來根據類創建對象使用new關鍵字。 def apply(num:Double,dev:Double) = new Infixtest(num,dev) //unapply方法接收一個對象,獲得後從中提取值————通常這些值就是當初用來構造該對象的值 def unapply(other:Infixtest) = { if(other.dev == 0) None else Some((other.num,other.dev)) } }
提取器的定義:
var Infixtest(a:Double,b:Double) = Infixtest(134.2,3) * Infixtest(2,12) println("Unapply方法提取器:"+ a + " " + b)
下面,使用提取器來獲取一個姓名的姓和名字符串:
/** * 實現unapply提取器,使用提取器來實現簡單功能,給一個姓名,分別提取出姓和名 * 定義unapply方法 */ object Name{ def unapply(other:String) ={ val names = other.split(" ") if (names.length != 2 ) None else Some( (names(0),names(1)) ) } }
測試類:
/** * 測試分拆姓名的unapply方法 */ val test_name = "Tom White" var Name(c,d) = test_name println("名是:" + c +" 姓是:" +d)
上邊例子,沒有定義Name類。Name對象是針對String對象的提取器。
另外,每一個樣例類都自動具備apply和unapply方法。例如:
case class Currency(value:Double,unit:String)
6.帶單個參數或無參數的提取器:
如果unapply方法要提取單值,則它應該返回一個目標類型的Option。提取器也可以只是測試其輸入而並不真的將值提取出來。
/** * 提取器提取一個或者不返回值 */ //將數字轉化爲Int object Number{ def unapply(num:String) = { Some(Integer.parseInt(num)) } } //確定字符串是否含空格 object IsCompond{ def unapply(num:String) = { Some(num.contains(" ")) } }
測試語句:
/** * 測試 :提取器提取一個或者不返回值 */ var Number(sf) = "332" var IsCompond(s) = "new York" println("數字解析:" + sf + ";Boolean解析:" + s)
7.unapplySeq方法:
要提取任意長度的序列,應該使用unapplySeq來命名方法。返回一個Option[Seq[A]],A是被提取的值的類型。
/** * 提取器提取一系列名字。 */ object Names{ def unapplySeq(args:String):Option[Seq[String]] = { //一個或多個空格分隔 if (args.split("\\s+").length > 1) { Some(args.split("\\s+"))} else None } }
測試:
/** * 測試unapplySeq方法 */ var Names(a1,a2,a3,a5,_,_) = "Let us go to park today!" println (a1+"||"+a2+"||"+a3+"||"+a5+"||")
需要注意的是,後邊拆分出來的元組,必須跟前邊Names()裏的變量個數一致,如果不需要的話用佔位符號。