scala 隐式转换- 写出你的笔记

欢迎转载 请注明出处

蚁人穿越到蜘蛛侠

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 = {...}

​ 隐式函数转换是提供一个从类型ST的转换即 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 PersonFormatter

    scala> 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