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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342