隐式变换也可以转换调用方法的对象,比如但编译器看到X.method,而类型X没有定义method(包括基类)方法,那么编译器就查找作用域内定义的从X到其它对象的类型转换,比如Y,而类型Y定义了method方法,编译器就首先使用隐含类型转换把X转换成Y,然后调用Y的method。
下面我们看看这种用法的两个典型用法:
支持新的类型
这里我们使用前面例子Scala开发教程(50): Ordered Trait中定义的Rational类型为例:
class Rational (n:Int, d:Int) { require(d!=0) private val g =gcd (n.abs,d.abs) val numer =n/g val denom =d/g override def toString = numer + "/" +denom def +(that:Rational) = new Rational( numer * that.denom + that.numer* denom, denom * that.denom ) def +(i:Int) :Rational = new Rational(numer +1*denom,denom) def * (that:Rational) = new Rational( numer * that.numer, denom * that.denom) def this(n:Int) = this(n,1) private def gcd(a:Int,b:Int):Int = if(b==0) a else gcd(b, a % b) }
类Rational重载了两个+运算,参数类型分别为Rational和Int。因此你可以把Rational和Rational相加,也可以把Rational和整数相加。
scala> val oneHalf = new Rational(1,2) oneHalf: Rational = 1/2 scala> oneHalf + oneHalf res0: Rational = 1/1 scala> oneHalf + 1 res1: Rational = 3/2
但是我们如果使用 1+ oneHalf会出现什么问题呢?
scala> 1 + oneHalf <console>:10: error: overloaded method value + with alternatives: (x: Double)Double <and> (x: Float)Float <and> (x: Long)Long <and> (x: Int)Int <and> (x: Char)Int <and> (x: Short)Int <and> (x: Byte)Int <and> (x: String)String cannot be applied to (Rational) 1 + oneHalf ^
整数和其相关类型都没定义和Rational类型相加的操作,因此编译器报错,此时编译器在1能够转换成Rational类型才可以编译过,因此我们可以定义一个从整数到Rational的隐含类型变换:
scala> implicit def int2Rational(x:Int) = new Rational(x) int2Rational: (x: Int)Rational
现在再执行1+oneHalf
scala> 1 + oneHalf res3: Rational = 3/2
在定义了int2Rational之后,编译器看到1+oneHalf,发现1没有定义和Rational相加的操作,通常需要报错,编译器在报错之前查找当前作用域从Int到其他类型的定义,而这个转换定义了支持和Rational相加的操作,发现int2Rational,因此编译器将 1+ oneHalf转换为
int2Rational(1)+oneHalf
模拟新的语法结构
隐式转换可以用来扩展Scala语言,定义新的语法结构,比如我们在定义一个Map对象时可以使用如下语法:
Map(1 -> "One", 2->"Two",3->"Three")
你有没有想过->内部是如何实现的,->不是scala本身的语法,而是类型ArrowAssoc的一个方法。这个类型定义在包Scala.Predef对象中。Scala.Predef自动引入到当前作用域,在这个对象中,同时定义了一个从类型Any到ArrowAssoc的隐含转换。因此当使用1 -> ?One?时,编译器自动插入从1转换到ArrowAssoc转换。具体定义可以参考Scala源码。
利用这种特性,你可以定义新的语法结构,比如行业特定语言(DSL)