dd

Scala 专题教程-隐式变换和隐式参数(4):转换被方法调用的对象

jerry Scala 2015年11月25日 收藏

隐式变换也可以转换调用方法的对象,比如但编译器看到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)

dd