1. 隐式规则
隐式定义指的是哪些我们允许编译器插入程序以解决类型错误的定义。
隐式转换受如下规则约束:
- 标记规则:只有标记为implicit的定义才可用。
- 作用域规则:被插入的隐式转换必须是当前作用域的单个标识符,或者跟隐式转换的源类型或目标类型有关联。
- 每次一个规则:每次只能有一个隐式定义被插入。
- 显示优先原则:只要代码按编写的样子能够通过类型检查,就不尝试隐式定义。
哪些地方会尝试隐式转换:
- 转换到一个预期的类型
- 对某个(成员)选择接受端(即字段、方法调用等)的转换
- 隐式参数
2. 隐式转换到一个预期的类型
每当编译器看见一个X而它需要一个Y的时候,它就会查找一个能将X转换成Y的隐式转换。
如:双精度浮点数不能转换为整数,会丢失精度
但可以定义一个隐式转换让它能够走下去:
在定义隐式转换之前需要导入包,博主未导入会报错:
import scala.language.implicitConversions
定义隐式转换:
再次运行代码:
3. 转换接收端
接受端是指方法被调用的那个对象。
-
与新类型互操作
假设有一个新的类,定义的 ** 方法,作用是求幂:
类定义:
没有隐式转换之前:
报错原因是因为1 是Int类型,而Int类型没有 ** 方法
定义隐式转换:
-
模拟新的语法
例如,Map的语法:
或许你会认为 -> 是Scala的语法特性,其实这是在scala.Predef对象里ArrowAssoc的一个隐式转换,如源代码:
-
隐式类
如上图,隐式类的格式为: implicit class xxx
隐式类有如下要求:
- 不能是样例类
- 构造方法必须有且仅有一个参数
- 隐式类必须存在于另一个对象、类或特质里面
4. 隐式参数
示例,用户命令提示字符串:
def greet 使用了柯里化,第二个参数开头使用了implicit,prompt和drink都是隐式参数
class PreferredPrompt(val preference: String)
class PreferredDrink(val preference: String)
object Greeter {
def greet(name: String)(implicit prompt: PreferredPrompt, drink: PreferredDrink) = {
println("Welcom, " + name + ". The system is ready.")
println("enjoy a cup of " + drink.preference + "?")
println(prompt.preference)
}
}
object My {
implicit val prompt = new PreferredPrompt("Yes, master> ")
implicit val drink = new PreferredDrink("tea")
}
运行,当你执行上面代码后,在命令行执行如下命令:
因为现在的作用域里没有可用的参数,需要进行导入
导入,并再次运行:
当然,也可以显示的给出参数:
另一个例子
def maxListOrdering[T](elements: List[T])
(ordering: Ordering[T]): T =
elements match {
case List() => throw new IllegalArgumentException("empty list!")
case List(x) => x
case x :: rest => {
val maxRest = maxListOrdering(rest)(ordering)
if (ordering.gt(x, maxRest)) x
else maxRest
}
}
函数的作用是按照Ordering[T]的比较规则来寻找elements列表的最值。
maxListOrdering接收一个List[T]作为入参,以及一个类型为Ordering[T]的入参。第二个入参是为了指定比较的顺序,这样,某些没有内建的顺序的类型也可以进行比较。
运行示例:
你可以看到,用起来很麻烦,必须给出一个显示的排序。
接下来江第二个参数标记为隐式的:
def maxListImpParm[T](elements: List[T])
(implicit ordering: Ordering[T]): T =
elements match {
case List() => throw new IllegalArgumentException("empty list!")
case List(x) => x
case x :: rest => {
val maxRest = maxListImpParm(rest)(ordering)
if (ordering.gt(x, maxRest)) x
else maxRest
}
}
其中ordering参数用来表述T的排序规则。具体因为是因为,Scala标准类库对许多常见的类型都提供了隐式的“排序”方法,所以这里可以直接来使用,如:
5. 上下文界定
在上面的代码种,你会发现有两处地方我们使用了第二个参数ordering:
那我们如和去掉对ordering的使用呢?
- 对于第一次使用,因为maxListImpParm本身第二个参数就是隐式参数,所以可以省略。
- 第二个参数,可以根据标准类库种定义了如下方法:
def implicitly[T](implicit t: T) = t
对比,(implicit ordering: Ordering[T]),所以我们只需要如下调用即可:
(implicitly[Ordering[T]].gt(x, maxRest)) x
所以,这个版本的代码为:
所以,这个版本的函数的方法体中,我们没有使用到第二个参数ordering,因此,这个参数名可以随便更改,而不用更改方法体的内容。
因为这样的操作很常用,Scala允许省这个参数,并使用上下文界定来缩短方法签名,如:
[T : Ordering]这样的语法就是一个上下文界定,它做了两件事:
- 像平常一样引入了一个类型参数T
- 添加了一个类型为Ordering[T]的隐式参数
6. 当有多个转换可选时
如果有多个隐式转换都符合某个场景,那它是如何选择呢?
Scala-2.7以前,如果发生多个隐式转换都符合的话,编译器会拒绝从中选择,即报错。
Scala-2.8以后,对其有所放宽:如果可用的转换当中有某个转换严格来说比其他的更具体,那么编译器就会选择这个转换。
满足如下条件时,就说某个隐式转换比另一个更具体:
- 前者的入参类型时后者入参类型的子类型
- 两者都是方法,而前者所在的类扩展自后者所在的类