一.本章要点

  • 隐式转换用于类型之间的转换  
  • 必须引入隐式转换,并确保它们可以以单个标识符的形式出现在当前作用域
  • 隐式参数列表会要求指定类型的对象。它们可以从当前作用域中以单个标识符定义的隐式对象的获取,或者从目标类型的伴生对象获取;
  • 如果隐式参数是一个单参数的函数,那么它同时也会被作为隐式转换使用
  • 类型参数的上下文界定要求存在一个指定类型的隐式对象
  • 如果有可能定位到一个隐式对象,这一点可以作为证据证明某个类型转换是合法的

二.隐式转换

  隐式转换函数指以implicit关键字声明的带有单个参数的函数。函数将被自动应用,将值从一种类型转换为另一种类型。例:

//将整数转换为分数
implicit def int2Fraction(n:Int)=Fraction(n,1)
//将自动调用int2Fraction(3),隐式转换为Fraction对象(转换函数名称可以随便,但是建议source2Target)
val result=3*Fraction(4,5)

三.利用隐式转换丰富现有类库的功能

  在Scala中,可以定义一个经过丰富的类型,提供自己想要的功能。例:

class RichFile(val from:File){
def read=Source.fromFile(from.getPath).mkString
}
//将原来的类型转换为新的类型,就可以在File对象上调用read方法了
implicit def file2RichFile(from:File)=new RichFile(from)

四.引入隐式转换

  Scala会考虑引入如下隐式函数:

    1.位于源或目标类型的伴生对象中的隐式函数;

    2.位于当前作用域可以以单个标识符指代的隐式函数

  如以上定义的int2Fraction函数,可以八它放在Fraction的伴生对象中,或者放到FractionConversions对象中(使用时需要引用它下面的方法才行,否则只能呢个以FractionConversions.int2Fraction调用如import com.horstmann.impatient.FractionConversions._而不是import com.horstman.impatient.FractionConversions)

  小技巧:在REPL中,输入:implicits查看所有除Predef引入的隐式成员,或者implicits -v查看全部  


五.隐式转换规则

  隐式转换在以下三种情况会被考虑:

      • 当表达式的类型与预期的类型不同时(如sqrt(Fraction(1,4))调用fraction2Double,因为sqrt预期是一个Double);
      • 当对象访问一个不存在的成员时(new File("README").read//将调用file2RichFile,因为File没有read方法);
      • 当对象调用某个方法,而该方法的参数声明与传入参数不匹配时:3×Fraction(4,5)//将调用int2Fraction,因为Int的*方法不接受Fraction作为参数

  不会尝试使用隐式转换:

      • 如果代码能够在不使用隐式转换的前提下通过编译,则不会使用隐式转换。例:如果a*b能够编译,那么编译器就不会尝试a*convert(b)或convert(a)*b;
      • 编译器不会尝试同时执行多个转换,比如convert1(convert2(a))*b;
      • 存在二义性的转换是个错误。如:如果convert1(a)*b和convert2(a)*b都是合法的,编译器将报错

    

Function(3,4)*5
//有如下两个表达式
Fraction(3,4)*intFraction(5)
fraction2Double(Fraction(3,4))*5
//都是合法的,这里不存在二义性,第一个转换胜出,不需要改变被应用*方法的那个对象

  小技巧:使用scala -Xprint:typer xxx.scala编译可以看到加入隐式转换的源码

六.隐式参数

  函数或方法可以带有一个标记为implicit的参数列表。这种情况下,编译器将会查找缺省值,提供给该函数或方法。  

七.利用隐式参数进行隐式转换

八.上下文界定

九.类型证明

十.@implicitNotFound注解

十一.CanBuildForm解读

十二.练习

12-16 08:01