操作符会涉及到以下部分的内容:
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()里的变量个数一致,如果不需要的话用占位符号。