使用 implicit 修饰的内容是隐式内容, 隐式的特点就是遇到适应的类型会自动的应用。隐式可以使得静态类型动态化,为现有类库添加功能,隐式的代理增强一个类或者一个方法。
隐式转化的时机
- 当方法中的参数的类型与目标类型不一致时
- 当对象调用所在类中不存在的方法或成员时,编译器会自动将对象进行隐式转换
隐式参数
定义
使用 implicit 修饰的变量称为 隐式变量,对应的变量值为 隐式值。定义的函数列表中存在使用 implicit 修饰的参数,这个参数就是隐式参数。需要注意的是,隐式参数需要放在参数列表的最后面。
传参优先级
Scala中函数的传参级别:传参 > 隐式值 > 默认值
① 在调用函数时直接传参数值,就用该参数值。
② 若未传参数值,则优先使用与隐式参数相同类型的隐式变量的值。注意:同类型的隐式变量只能有一个。
③ 前两者都没有的话,就用该参数的默认值。
示例
// 第二个参数默认值为5
def sum(a:Int)(implicit b:Int = 5):Int = {
a + b
}
// 第二个参数使用指定传值3
val m = sum(10)(3)
// 未指定第二个参数,传参数默认值5
val n = sum(10)
// 定义隐式变量,隐式值为10
implicit val num:Int = 10
// 第二个参数使用指定传值8
val p = sum(10)(8)
// 未指定第二个参数,传隐式值 10
val q = sum(10)
隐式函数
定义
使用 implicit 修饰的函数就是 隐式函数,也称为 隐式转换。
应用场景
可以通过隐式函数将一种类型转换为另一种类型,主要用来做 类型转换 和 类型增强 。输入 :implicit -v ,可查看Scala中定义的隐式函数和隐式类。
类型转换
当声明一个变量的类型与赋值类型不一致时,会直接报错,但若定义了一个可以将赋值类型转换为变量类型的隐式函数,则会自动对赋值类型做转换。
示例
// 报错:类型不匹配
val a:Int = 2.5
// 定义隐式参数
implicit def doubleToInt(dbl:Double) = dbl.toInt
// Double值会自动调用隐式转换,变为Int值
val b:Int = 2.5
类型增强
有些类型不能直接参与计算,比如Boolean,但若是给它做个隐式转换,就可以参与计算了。
// 报错:不能接受Boolean类型参与计算
val a = 1 + true
// 定义隐式函数
implicit def booleanToInt(bln:Boolean) = if(bln) 1 else 0
// true 转换为 1 false 转换为 0
val b = 1 + true
隐式类
定义
使用 implicit 修饰的类就是 隐式类。隐式类可以在不改变目标类型源代码的情况下,扩展目标类的功能。
约束
- 必须定义在trait/class/object里(不能独立定义)
- 构造器只能带一个不是implicit修饰的参数
- 作用域中不能有任何与隐式类同名的方法、成员或对象
示例
class B{
val add = (x:Int, y:Int) => { x + y }
}
object ImplicitCtx{
// 隐式类只能对一种类型的类进行增强,B类及其子类
implicit class RichB(b:B){
def multiply(x:Int, y:Int) = { x * y }
}
}
val b = new B
val m = b.add(2, 3)
// 引入隐式类后,就可以执行增强功能
import ImplicitCtx._
val n = b.multiply(2, 3)
隐式解析机制
即编译器是如何查找到缺失信息的,解析具有以下两种规则:
- 首先会在当前代码作用域下查找隐式实体(隐式方法 隐式类 隐式对象)
- 如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。
类型的作用域是指与该类型相关联的全部伴生模块,一个隐式实体的类型T它的查找范围如下:
- 如果T被定义为T with A with B with C,那么A,B,C都是T的部分,在T的隐式解析过程中,它们的伴生对象都会被搜索。
- 如果T是参数化类型,那么类型参数和与类型参数相关联的部分都算作T的部分,比如List[String]的隐式搜索会搜索List的伴生对象和String的伴生对象
- 如果T是一个单例类型p.T,即T是属于某个p对象内,那么p对象也会被搜索
- 如果T是类型注入S#T,那么S和T都会被搜索