implicit关键字是scala中一个比较有特点的关键字,他保证了scala在很多时候没有一些不必要的代码冗余,使得scala看起来更加整洁,同时使得scala的一些库在设计的时候,可以有更加直观的操作方法。
隐式参数
隐式参数(implicit parameters)是最简单的形态,也是最容易理解的。与函数中增加默认值的效果类似,但是更为灵活。
object ImplicitParameters{
implicit val name: String = "default" // name名称可与下面参数名称不同,他是根据变量类型进行推导替换的
log("init")
def log(msg:String)(implicit name:String): Unit = { println(s"[$name] $msg ")}
def process(): Unit = {
implicit val name = "process"
log("doing something")
}
def main(args: Array[String]): Unit = {
implicit val name = "main"
log("start")
process()
log("end")("custom name")
}
}
// 输出如下
[default] init
[main] start
[process] doing something
[custom name] end
在运行时,函数的隐式参数会自动去寻找符合条件类型的隐式变量,然后进行变量替换。需要注意的是,想同类型的隐式变量只能定义一个,不然会报错,编译器解析的时候不知道该替换哪一个。
隐式参数有三种定义方法:
-
def func(implicit x: Int)
, x是implicit的; -
def func(x: Int)(implicit y:Int)
, y是implicit的; -
def func(implicit x:Int, y:Int)
, x, y都是implicit的;
以下定义方式是错误的,无法通过:
def err(x: Int, implicit y:Int)
def err(implicit x:Int)(implicit y:Int)
def err(implicit x:Int)(y:Int)
隐式函数
隐式函数用于类型转换。如当函数输入类型与实际要求类型不符合时,编译器会自动搜寻可用的隐式函数对数据进行类型转换,使得程序正常运行。相当于给了一个修复编译器调用错误的机会,同时也赋予了强大的扩展能力,可以更为方便地实现 DSL。
import scala.language.implicitConversions
//这个import是为了避免出现warning
object Test extends App {
implicit def conv(a: Int) = {
println("in conv")
a.toString
}
def say(b: String) = println(b)
say(5)
}
//输出结果:
// in conv
// 5
//这说明过程是say(conv(5))
//原因是编译器在检查的时候发现需要一个String类型的参数,但是代入的是一个Int,于是
//他会在范围内寻找implicit的function,找到了符合这个要求的String => Int的function,然后调用
隐式类
implicit class MyClass(x: Int)
隐式类是隐式函数的一个语法糖(一个不恰当的比喻是Python的装饰器函数和装饰器类)。其作用主要是类的主构造函数可以作为隐式转换的参数,相当于其主构造函数可以用来当做一个implicit的function,如下例:
object Test{
implicit class MyName(x:Int){
println("I'm in cons")
val y = x
}
def say(x:MyName) = {
println(x.y)
}
say(5)
}
# 输出结果
I'm in cons
5
这里的MyName是一个隐式类,其主构造函数可以用作隐式转换,所以say需要一个MyName类型的参数,但是调用的时候给的是一个Int,这里就会调用MyName的主构造函数转换为一个MyName的对象,然后在println其y的值。
隐式转换规则
1、标记规则:隐式转换中涉及到的变量、函数、类都是带有implicit关键词的,也就是说隐式转换必须有implicit定义才能生效;
2、作用域规则:在隐式转换使用的位置必须是单标识符可见的,也就是可以无前缀引用,例如,可以是x,但不可以是foo.x;
3、作用域规则延伸:作用域规则未找到的情况下,会在类型的隐式作用域(伴生对象)内查找,吟诗作用域是指与该类型相关联的全部伴生模块;
4、代码优先规则:隐式转换触发的时机是在编译器出现了查找方法失败的情况下才会被触发,因此如果代码可以正常执行的话,是不会触发隐式转换的;
5、有且只有一次隐式转换规则:触发一次隐式转换,只能转换一次。如x+y的x触发隐式转换,只会被隐式转换为convert(x)+y,而不会进行两次隐式转换convert2(convert1(x))+y。
需注意的点:
1、在使用的时候,不要再一个作用域内不要定义多个相同类型的隐式变量,因为隐式变量是根据类型匹配,所以定义多个相同类型的隐式变量,会报编译错误,编译器无法进行选择。另外,隐式变量本身也是可以在作用域内使用和变量一样的遮蔽(shadow)。实践中,建议新建特定的只包含一个变量的类,来保证可以明确地进行预期的隐式转换,不会被其他不经意的代码 shadow;
2、在使用隐式函数的时候,请 import scala.language.implicitConversions,否则编译的时候会有一个警告:warning: there was one feature warning; re-run with -feature for details。
网友评论