对scala的隐式参数又爱又恨,既强大得让人欢呼雀跃,但繁琐的机制与晦涩的语法又让人望而却步,总结起来,implicit一共有三种用法,一种比一种强大,这里就不讲太深奥的理论,直接进入应用场景实战。
1. 默认参数值
首先,需要注意的是,在方法参数上添加implicit关键字对方法几乎没有任何影响,原来的调用方式依旧。
其次,implicit作为参数声明时,只能作为方法的唯一参数,是的,你没看错,唯一的参数,还有每个方法也只能声明一个参数!如果混在一起,那么它几乎没有作用,因为每一次调用时你都必须指定方法的所有参数。当然,scala提供了完美的解决办法,请看下面的例子:
test("隐式参数必须独立声明") {
// 调用时无法省略参数
def add(implicit a: Int, b: Int): Int = a + b
// 一定要作为最后一个参数
def plus(a: Int)(implicit b: Int): Int = a + b
// 声明隐式参数,不需要名称相同,只能类型相同就可以了
implicit val c: Int = 10
// 单元测试
assertResult(plus(5))(15)
}
从上面的例子可以得出如下结论:
1. 通过高阶函数的方法,隐式参数可以和其他参数一起合作,但受限于调用方式,必须作为函数的最后一个参数;
2. 声明隐式参数时,并不需要采用与函数参数一样的名称,只要类型相同即可;
2. 参数类型转换
在方法的调用中,实参的类型有时难以匹配形参的类型,此时需要可以利用隐式方法进行自动转换
test("隐式方法转换类型") {
// 调用的方法,并不需要声明为隐式参数
def plus(a: Int, b: Int): Int = a + b
// 定义转换函数
implicit def stringToInt(s: String): Int = Integer.parseInt(s)
assertResult(plus("3", "5"))(8)
}
从上面的例子中,我们可以看出:
1. 调用的方法不需要携带隐式参数,适用于所有的方法;
2. 定义转换函数后,大大增强了方法参数的适配性;
3. 隐式函数查找的作用域跟隐式参数的作用域一模一样;
3. 动态添加属性与方法
除了可以批量赋值默认参数,以及动态转换函数外,implicit最强大的地方还在于能为每种类型动态添加属性与方法,如下:
test("动态添加属性与方法") {
// 参数就是添加属性与方法的类型
implicit def strFile(dir: String) = new {
// 动态添加属性
val isDir:Boolean = true
// 动态添加方法
def listDir(): List[File] = {
val d = new File(dir)
// _只有作为参数时才能省略,作为主体时不能省略
d.listFiles().filter(_.isDirectory).toList
}
}
assert("/".listDir().size > 0)
assert("/".isDir)
}
这个功能简直强大到吓人,任意类型一律通杀,无视类型的声明是否是不变模型(final),无视类型是自定义还是系统内置。有了这样的机制,写出一万公里长的链式代码也是易如反掌。
4. 隐式作用域
为了找到相关的隐式参数与方法,scala会按如下优先级查找相关的隐式声明:
1. 在当前函数调用的作用域及父作用域内;
2. 在声明隐式参数类型的伴生作用域内;
结论
简简单单的implicit关键字,对Scala的功能增强几乎是天翻地覆,只有理解了implicit,尤其是第三种用法,才能理解如同天书一样的DSL代码(尤其是初次使用的JAVA程序员),强烈推荐《深入理解Scala》(这本书翻译得不太好,但内容很有深度)进一步阅读。