1.隐式转换:隐式转换可以减少从一个类型显式转换成另一个类型的需要。Java的监听器:
val button=new JButton
button.addActionListener(
new ActionListener{
def actionPerformed(event: ActionEvent)={
println("pressed!")
}
}
}
这段代码当中有大量不增加有用信息的样板代码。这个监听器是一个ActionListener,回调方法的名称为actionPerformed,以及入参是一个ActionEvent,这些信息对于任何传给addActionListener的入参而言都是不言而喻的。这里唯一的新信息是要被执行的代码,也就是对println的调用。对Scala更友好的版本应该接收函数作为入参:
button.addActionListener( //类型不匹配!
(_: ActionEvent)=>println("pressed!")
)
按目前这样的写法,这段代码不能正常工作。addActionListener方法想要的是一个动作监听器,而我们给它的是一个函数。而通过隐式转换,这段代码是可行的:
implicit def function2ActionListener(f: ActionEvent => Unit)=
new ActionListener{
def actionPerformed(event:ActionEvent)=f(event)
}
这里是一个从函数到动作监听器的隐式转换:
//现在可以了
button.addActionListener(
(_: ActionEvent)=>println("pressed!")
)
这段代码之所以可行,编译器首先会照原样编译,不过会遇到一个类型错误。在放弃之前,它会查找一个能修复该问题的隐式转换。在本例中,编译器找到了fuction2ActionListener。它会尝试这个隐式转换,发现可行,就继续下去。
2.隐式规则:隐式定义指的是那些我们允许编译器插入程序以解决类型错误的定义。
隐式转换受如下规则的约束:
- 标记规则:只有标记为implicit的定义才可用。可以用implicit来标记任何变量、函数或对象定义。
- 作用域规则:被插入的隐式转换必须是当前作用域的单个表示符,或者跟隐式转换的源类型或目标类型有关联。
- 每次一个规则:每次只能有一个隐式定义被插入。
- 显式优先规则:只要代码按编写的样子能通过类型检查,就不尝试隐式定义。
命名一个隐式转换:隐式转换可以用任何名称。隐式转换的名称只在两种情况下重要:当你想在方法应用中显式地写出来,以及为了决定在程序中的某个位置都有哪些隐式转换可用时:
object MyConversions{
implicit def stringWrapper(s:String):WrappedString=wrapString(s)
implicit def intToString(x:Int):String=x.toString
}
在你的应用程序中,你想使用stringWrapper转换,不过并不希望整数通过intToString自动转换成字符串。可以通过只引用其中一个转换而不引用另一个来做到:
import MyConversions.stringWrapper
3.转换接收端:隐式转换还能应用于方法调用的接收端,也就是方法被调用的那个对象。
模拟新的语法:隐式转换的另一个主要用途是模拟添加新的语法。回想一下我们曾经提到过的,可以用如下的语法来制作一个Map:
Map(1->"one",2->"two",3->"three")
你有没有想过Scala是如何支持 -> 这个写法的?这并不是语法特性!-> 是ArrowAssoc类的方法:
implicit final class ArrowAssoc[A](private val self: A) extends AnyVal {
@inline def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
def →[B](y: B): Tuple2[A, B] = ->(y)
}
隐式类:Scala2.10引入了隐式类来简化富包装类的编写。隐式类是一个以implicit关键字打头的类。对于这样的类,编译器会生成一个从类的构造方法参数到类本身的隐式转换:
case class Rectangle(width:Int,height:Int)
implicit class RectangleMaker(width:Int){
def x(height:Int)=Rectangle(width,height)
}
上述代码以通常的方式定义了一个RectangleMaker类。不仅如此,它还自动生成了如下转换:
implicit def RectangleMaker(width:Int)=new RectangleMaker(width)
这样一来,你就可以通过在两个整数之间放一个x来创建点:
val a=3 x 4
println(a)
//打印:Rectangle(3,4)
给那些喜欢冒险的朋友提个醒:你可能会觉得任何类定义前面都可以放implicit。并非如此,隐式类不能是样例类,并且其构造方法必须有且仅有一个参数。不仅如此,隐式类必须存在于另一个对象、类或特质里面。
4.隐式参数:编译器会插入隐式定义的最后一个地方是参数列表。编译器有时候会将someCall(a)替换为someCall(a)(b),或者将new Some(a)替换成new Some(a)(b),通过追加一个参数列表的方式来完成某个函数调用。隐式参数提供的是整个最后一组柯里化的参数列表,而不仅仅是最后一个参数:
def maxListImpParm[T](elements:List[T])(implicit ordering:Ordering[T]): T ={
elements match {
case Nil=>throw new IllegalArgumentException("empty")
case List(x)=>x
case x::rest=>{
val maxRest=maxListImpParm(rest)
if(ordering.gt(x,maxRest)) x else maxRest
}
}
}
implicit val ordering=Ordering.Int
5.当有多个转换可选时:到Scala2.7为止,只要有多个隐式转换同时可用,编译器就会拒绝从中进行选择,需要显示写出。Scala2.8对这个规则有所放宽。如果可用的转换当中有某个转换严格来说比其他的更具体,那么编译器就会选择这个更具体的转换。更确切地说,当满足下面任意一条时,我们就说某个隐式转换比另一个更具体:
- 前者的入参类型是后者入参类型的子类型。
- 两者都是方法,而前者所在的类扩展自后者所在的类。