深度剖析scala中的隐式转换

“道常无为,而无不为。
侯王若能守之,万物将自化。
化而欲作,吾将镇之以无名之朴。
无名之朴,夫亦将不欲。
不欲以静,天下将自定。”[1]

对于scala的学习成本,大多会体现在对于隐式转换的理解应用上。隐式转换伴随着泛型,scala所有的神秘之处、优雅之处皆因于此。

隐式在scala中,应用场景会在如下四个方面:

  • 隐式参数
scala> def razor(implicit name:String) = name
razor: (implicit name: String)String

直接调用razor方法

scala> razor
<console>:13: error: could not find implicit value for parameter name: String
       razor
       ^

报错!编译器说无法为参数name找到一个隐式值
定义一个隐式值后再调用razor方法

scala> implicit val iname = "Philips"
iname: String = Philips

scala> razor
res12: String = Philips

因为将iname变量标记为implicit,所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺少参数。
但是如果此时你又重复定义一个隐式变量,再次调用方法时就会报错

scala> implicit val iname2 = "Gillette"
iname2: String = Gillette

scala> razor
<console>:15: error: ambiguous implicit values:
 both value iname of type => String
 and value iname2 of type => String
 match expected type String
       razor
       ^

匹配失败,所以隐式转换必须满足无歧义规则,在声明隐式参数的类型是最好使用特别的或自定义的数据类型,不要使用Int,String这些常用类型,避免碰巧匹配。

  • 隐式函数
class Person(val name:String)

class Engineer(val name:String,val salary:Double){
  def mySalary(): Unit ={
     println(name+",your monthly salary is is:"+salary)
  }
}

object Test {
  def main(args: Array[String]): Unit = {
    new Person("sutong").mySalary
  }
}

实例化一个person对象,然后调用mySalary方法时,此时编译是通不过的,因为person类没有mySalary方法。但是如果我们加入一个隐式函数就能时编译通过并能顺利运行,如下:

object Test {
  implicit def person2Engineer(p:Person):Engineer = {
    new Engineer(p.name,100000)
  }
  def main(args: Array[String]): Unit = {
    new Person("sutong").mySalary
  }
}

输出:

sutong,your salary is:100000.0

Scala会在报错前,去找隐式函数,例如我们定义的隐式函数,根据函数签名,主要是输入参数,找到了person2Engineer方法,并利用此方法将person对象转换为了Engineer对象,然后发现Engineer对象有mySalary方法,直接调用其mySalary方法,打印输出。运行后,Person对象和Engineer类型无任何关系。
约定:

虽然在定义implicit函数时scala并未要求要写明返回类型,但我们应写明返回类型,这样有助于代码阅读。

  • 隐式对象
    用implicit修饰的object就是隐式对象。
abstract class Template[T]{
  def add(a:T,b:T):T
}

abstract class SubTemplate[T] extends Template[T] {
  def unit:T
}

object Implicit_Object_Test {
  def main(args: Array[String]): Unit = {
    implicit object StringAdd extends SubTemplate[String]{
      override def unit: String = ""
      override def add(a:String,b:String):String = a concat b
    }
    implicit object IntAdd extends SubTemplate[Int]{
      override def unit: Int = 0
      override def add(a:Int,b:Int):Int = a+b
    }

    def sum[T](x:List[T])(implicit m:SubTemplate[T]):T = {
      if(x.isEmpty) m.unit
      else m.add(x.head,sum(x.tail))
    }

    println(sum(List(2,4,6,8,10)))
    println(sum(List("a","b","c")))
  }
}
输出:
30
abc

首先定义了两个含有泛型的抽象类,Template是父类,而SubTempalte是子类,而这个子类有有两个子对象StringAdd和IntAdd,下面来看下主函数中定义的sum函数,它是一个含有泛型的函数,它的第一个参数是含有泛型的List类型的xs,第二个参数是含有泛型的SubTemplate类型的隐式参数m,函数返回值是一个泛型,首先,函数里先判断传入的第一个参数是否为空,若为空则调用隐式参数m,有由于scala可以自动进行类型推到,所以运行时,泛型T是一个确定类型,要么为Int要么为String,但是为空时,我们调用隐式参数m的unit是不同的,Int为0,而String为“”,所以我们定义了两个隐式对象对其进行处理,IntAdd隐式类复写了unit,使之为0,StringAdd隐式类复写了unit,使之为””,这样程序就可以正常执行了。同理,隐式对象方法对add方法进行复写,完成了sum操作。

  • 隐式类
    在scala2.10后提供了隐式类,可以使用implicit声明类,但是需要注意以下几点:
    1.其所带的构造参数有且只能有一个
    2.隐式类必须被定义在类,伴生对象和包对象里
    3.隐式类不能是case class(case class在定义会自动生成伴生对象与2矛盾)
    4.作用域内不能有与之相同名称的标示符
import scala.io.Source
import java.io.File

object Implicit_Class {
  import Context_Helper._
  def main(args: Array[String]) {
    println(1.add(2))  
    println(new File("I:\\aa.txt").read())
  }
}

object Context_Helper{
  implicit class ImpInt(tmp:Int){
    def add(tmp2: Int) = tmp + tmp2
  }
  implicit class FileEnhance(file:File){
    def read() = Source.fromFile(file.getPath).mkString
  }
}

其中1.add(2)这个可以这样理解:

当 1.add(2) 时,scala 编译器不会立马报错,而检查当前作用域有没有 用implicit 修饰的,同时可以将Int作为参数的构造器,并且具有方法add的类,经过查找 发现 ImpInt 符合要求,利用隐式类 ImpInt 执行add 方法。

归纳一下

  • 隐式转换的时机
    a.当方法中的参数的类型与目标类型不一致时
    b.当对象调用类中不存在的方法或成员时,编译器会自动将对象进行隐式转换
  • 隐式解析机制
    a.首先会在当前代码作用域下查找隐式实体(隐式方法 隐式类 隐式对象)
    b.如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找
  • 隐式转换的前提
    a.不存在二义性
    b.隐式操作不能嵌套使用
    c.代码能够在不使用隐式转换的前提下能编译通过,就不会进行隐式转换

  1. 老子《道德经》第三十七章,老子故里,中国鹿邑。

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

推荐阅读更多精彩内容