在学习Scala的时候,隐式转换(implicit conversion)这个特性让我实在是闹不住啊。因而乎一边试用一边感慨:真的是太强大,太方便了。
不过,越是强大且方便的东西,越容易用出毛病来。在我不求甚解的状况下,毛病就来了,我把它称为隐式转换优先顺序问题:
假设咱们有一个表示文本的行数的类LineNumber: 工具
class LineNumber ( val num : Int )
咱们能够用这个类来表示一本书中每一页的行数:
val lineNumOfPage1 = new LineNumber(112)
val lineNumOfPage2 = new LineNumber(120)
上面的代码分别表示了第一页和第二页的行数。固然,咱们也应该能够将它们相加,获得这两页的总行数:
val totalLineNum = lineNumOfPage1 + lineNumOfPage2
这样的话,咱们的LineNumber类里就应该有一个 “+” 方法来将两个对象相加:
class LineNumber ( val num : Int ) {
def + ( that : LineNumber ) = new LineNumber( this.num + that.num )
}
从直观上讲,咱们甚至能够直接用整数来和LineNumber相加:
val totalLineNumber = lineNumOfPage1 + 120
//或者
val totalLineNumber = 112 + lineNumOfPage2
写到这里咱们发现,LineNumber对象中没有接受整型做为参数的 “+” 方法;而整型中也不会有接受LineNumber做为参数的 “+” 方法。
为了说明问题,咱们不在LineNumber类中增长一个整型做为参数的重载方法,而是定义两个隐式转换,一个将Int转换为LineNumber,另外一个将LineNumber转换为Int:
object LineNumber{
implicit def intToLineNumber( i : Int ) = new LineNumber(i)
implicit def lineNumberToInt( o : LineNumber ) = o.num
}
接下来再让咱们尝试用LineNumber和整型相加:
import LineNumber._
val totalLineNumber1 = lineNumOfPage1 + 120
val totalLineNumber2 = 112 + lineNumOfPage2
咱们会获得什么结果呢?
我最初认为,Scala编译器可能会产生编译错误,毕竟咱们如今的代码中对于两种类型的隐式转换都是存在的,这样的话上面两个语句的方法参数和调用方法的对象均可以作隐式转换。很明显我错了。运行结果:totalLineNumber1是一个新的LineNumber的对象,而且它的字段num会是232;而totalLineNumber2则是一个Int类型,值为232。
先让咱们看看在编译时加入 “-Xprint:typer” 后上面两句的结果:
val totalLineNumber1: LineNumber = lineNumOfPage1.+(LineNumber.intToLineNumber(120));
val totalLineNumber2: Int = 112.+(LineNumber.lineNumberToInt(lineNumOfPage2));
到这里已经很清楚了。Scala编译器优先选择了方法的参数做为转换对象,而没有选择调用方法的对象。这是为何呢?
其实,当程序类型检查出现问题的时候,Scala编译器会在两个位置上对出现问题的语句尝试使用隐式转换:
- 第一个位置是 “能够直接将某个类型转换为指望的类型” 的地方
- 第二个位置是 “能够将调用方法的对象转换为合适类型” 的地方
第一个位置是那种咱们须要A可是传入的倒是B的地方。上面的例子正好就是这样的状况,对于totalLineNumber1来讲,lineNumOfPage1的 “+” 方法须要的参数类型是LineNumber型,然而咱们传入的是Int类型,Scala编译器在这里检测到类型错误后,会寻找将Int转换为LineNumber的隐式转换。由于在代码开始时,咱们利用 "import LineNumber._ " 导入了相应的隐式转换,所以Scala编译器使用导入的隐式转换将整型120转换为了LineNumber类型。totalLineNumber2发生的状况是相同的,只不过是将LineNumber类型转换为了Int类型。
也就是说,Scala编译器在使用隐式转换的时候,首先回去寻找能够直接转换类型的位置,其次才对调用方法的对象。若是咱们将咱们的隐式转换中intToLineNumber的转换去掉,那么咱们获得的totalLineNumber1就会变为一个整型。
就像我开头所说的那样,Scala的隐式转换是一个很是强大的工具,可是若是不了解它,或者将其滥用在程序中,会使得程序难以理解和维护。真的到了那个时候,就确实“闹不住”了。