欢迎转载 请注明出处
scala隐式类型对精简代码和提升可读性上有很大的帮助,一直是解零零碎碎学习,从没有系统梳理过。从时间线上看,我们理解知识的路线并不是从入门到精通,更多的是是从入门到迷糊\放弃。
现在记录使用时的方方面面。不难哦
,因为不涉及理论和编译层面的内容。
scala的类型推导系统很强大,引入隐式类型在精简上简直是如虎添翼。对我们阅读者而言,隐式类型提升程序扩展性和灵活性同时,也引发可读性的另一个争论-代码可读性问题。 在这里,我们不辩争议,只述说功能。
内容提要
本文内容述说隐式类型的用途,以及使用时的约束。
每一种隐式类型从使用方法
、完整类型签名
、例子
和使用中的约束
进行说明。
基本隐式类型解析(编译器是如何查找到缺失信息解析)规则如下:
1.首先会在当前代码作用域下查找隐式实体(隐式方法 隐式类 隐式对象)
2.如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找
详细的解析方式可参考 官方隐式类型推导QA
隐私类型声明
scala中隐式类型定义关键字 implicit
.在隐式类型前使用 implicit
即可。以隐式类为例:
object Helpers {
implicit class IntWithTimes(x: Int) {
def times[A](f: => A): Unit = { }
}
}
上例中 IntWithTimes
是一个隐式类.使用隐式类,导入 Helpers
对象,我们调用times
时,不再需要new IntWithTimes
.那么,implicit
关键字可作用于哪些类型?又如何使用哪?下面一一说明。
隐式类型使用规则
作用域规则:不管是隐式值,隐式对象,隐式类或隐式转换函数,都必须在当前的作用域使用才能起作用.
一次性转换规则:隐式转换从源类型到目标类型只会经过一次转换,不会经过多次隐式转换达到。嵌套转换不行
不存在二义性:即相同的类型转换,不能定义两种方式。
隐式类型方法列表
隐式类型方法之:隐式函数转换 (implicit conversion)
- 隐式函数转换签名:
implicit def name(s:T) :U = {...}
隐式函数转换是提供一个从类型S
到T
的转换即 S
=>T
。
-
实例
case class Person(id:Int = 1,age:Int = 18,add:String = "earth") object ImplicitExample extends LogComponent{ //隐式函数 参数传递值 implicit def personToInt(person: Person): Int = person.age }
在
Repl
的运行结果:scala> Person() * 1 res1: Int = 18
Person()* 1
运行,Person类型无法直接进行乘法运算.编译器需要查询基础类型(Int/Double/Float)和整型进行乘法,在作用域内查找到personToInt
方法时,即完成了乘法类型匹配. scala 不鼓励使用隐式转换,建议使用后面的隐式参数替代。
隐式类型方法之:隐式值 implicit val/var
隐式值签名方式:
implict var/val varName: Type = ???
-
实例
implicit val implicitValue : Int = 10 //定义implicitValue 作为隐式变量
。隐式值是隐式类型转换中使用较少的一种。主要供隐式参数使用。后面会说明隐式参数。
隐式参数转换和隐式值使用时,要定义内类/trait/object等类型内。
隐式类型方法之: 隐式类implict class
隐式类签名:
implicit calss name(paraName:Type) {....}
-
实例
implicit class PersonFormatter(person: Person) { def toImplicitInt: Int = person.age }
Repl中运行结果:
`scala> implicit class PersonFormatter(person: Person) {
| def toImplicitInt: Int = person.age
| }
defined class PersonFormatterscala> Person().toImplicitInt
res2: Int = 18`上例中对于实现类型
Person
=>Int
转换,使用时,Person对象调用方法toImplicitInt
即可,无需new PersonFormatter
对象。 -
使用约束/限制
-
隐式类只能在trait/类/对象内部定义
object Helpers { implicit class RichInt(x: Int) // 正确! } implicit class RichDouble(x: Double) // 错误!
即隐式类不能单独定义,需要归属于其它类型内部
-
样例类(case class)不能是隐式类
implicit case class T(person: Int) {} //编译错误
样例类在实例化时,会自动生成伴生对象。这与第一条的约束有冲突。
-
隐式类必须携带参数,且非隐式参数只能包含一个参数
implicit class RichDate(date: java.util.Date) // 正确! implicit class Indexer[T](collecton: Seq[T], index: Int) // 错误! implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正确! implicit class PersonFormatter {} // 无非隐式参数,编译错误
-
-
应用场景
隐式类主要用于功能扩展。如依赖第三方依赖库,不修改依赖库的前提下,扩展现有功能。第三方库变化,对扩展功能修改也是可控的。
隐式类型方法之:隐式参数 implicit parameters
参数签名:隐式参数是隐式类型应用较多的一类。它的类型可以是函数参数、类、对象、值等
-
实例
-
函数使用隐式参数
implicit val person = Person() def implicitPara(implicit sec: Person ) = sec.age //参数是隐式类型
Repl
执行结果:scala> implicitPara res6: Int = 18
函数柯理化参数使用隐式参数
implicit val person = Person() def implicitPara( one:Int)(implicit sec: Person ) = one + sec.age
以柯理化函数为例,定义隐式值
person:Person
,函数implicitPara
中sec是隐式值类型,调用时,如果传入sec
编译器在作用域范围内查询,找不到就发生编译错误。Repl
运行结果:scala> implicitPara(1) //person age默认值18 res5: Int = 19
-
隐式参数是对象
这一类在后面涉及隐式对象时详细说明。
-
-
使用约束/限制
-
全部是隐式参数:函数没有柯理化时,implicit关键字作用域参数列表中所有参数
//没有柯理化函数 def implicitPara( one:Int,implicit sec: Person ) = one + sec.age //错误 ! def implicitPara( implicit one:Int, sec: Person ) = one + sec.age //one sec 都是隐式参数
不支持部分参数使用隐式参数。
-
部分隐式参数:函数柯理化时,可部分指定隐式参数
上例说函数使用隐式参数必须全部使用。那么,若想部分执行隐式参数,只能使用柯理化的函数。虽然如此,函数参数也只能最后一个参数使用
implicit
使用,否则编译器报错。//柯理化函数 def implicitPara( one:Int)(sec:Int)(implicit third: Person ) = one + third.age + sec def implicitPara( one:Int)(implicit sec:Int,third: Person ) = one + third.age + sec //编译错误
隐式参数中,隐式类型
impilcit
关键字只能出现一次,柯理化函数也不例外。 -
隐式类型方法之:隐式对象 implicit object
隐式对象签名 :
implict object objName { body }
-
实例
object Example{ implicit object InnerObject { def printAge(person:Person) :Int = { println("enter object ImplicitInnerObject") person.age } } }
Repl
运行结果:import Example.InnerObject._ def fun = {import Example.InnerObject._ ;printAge(Person())}scala> fun enter object ImplicitInnerObject res2: Int = 18
-
使用约束/限制
隐式对象不能作为隐式参数。隐式对象如何使用哪?如下代码:
//定义trait trait ImplicitInnerTrait { println("enter trait ImplicitInnerTrait") def printAge(person:Person) :Int //抽象方法 } //定义隐式对象 implicit object ImplicitInnerObject extends ImplicitInnerTrait{ def printAge(person:Person) :Int = { //重写方法 println("enter object ImplicitInnerObject") person.age } } //定义函数,参数包含隐式参数对象 //def callImplic(person:Person)(implicit o:ImplicitInnerObject) = { //错误! def callImplic(person:Person)(implicit o:ImplicitInnerTrait) = { println("call implicit") o.printAge(person) }
隐式类型方法之:隐式宏 implict macro
隐式宏类型2.10.0版本发布的实验功能,该功能不在本文说明范围内。感兴趣的童鞋,参见官方隐式宏介绍
-
~~类型约束,隐式转换
<%<~~
A<%<B 类似于view bound,表示 A可以当作B,即A隐式转换成B也满足。从查到的资料看在2.10版本已经废弃。
在泛型中,隐式类型应用有稍许差异,如T<% X
协变除包含T``X
的包含关系外,还支持隐式类型的转换。
总结
以上,着重介绍了隐式类型功能,对各类型的如何声明/使用,以及约束都有描述。理解本文满足解决普通隐式类型如何使用问题。随着对隐式类型的使用,我们的理解也会越来越深入。
最后,本文虽然不是一篇隐式类型从入门到精通文章,至少是一篇深入浅出的问题吧。
坊间传闻,点赞有人爱▄█▀█给跪了
* 参考文档列表 *
https://docs.scala-lang.org/tour/implicit-parameters.html#inner-main
https://docs.scala-lang.org/zh-cn/overviews/core/implicit-classes.html
https://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html