关于 Scala 界定、隐式转换的一些知识(六)——隐式转换 和 隐式参数

1. 隐式转换简介

在scala语言当中,隐式转换是一项强大的程序语言功能,它不仅能够简化程序设计,也能够使程序具有很强的灵活性。

在scala语言中,隐式转换是无处不在的,只不过scala语言为我们隐藏了相应的细节,例如scala中的类继承层次结构中:



它们存在固有的隐式转换,不需要人工进行干预,例如Float在必要情况下自动转换为Double类型

在前面 关于 Scala 界定、隐式转换的一些知识(三)——视图界定 中我们也提到,视图界定可以跨越类层次结构进行,它背后的实现原理就是隐式转换,例如 Int 类型会视图界定中会自动转换成 RichInt, 而 RichInt 实现了 Comparable 接口,当然这里面的隐式转换也是 scala 语言为我们设计好的

本节将对隐式转换中的隐式转换函数、隐式转换规则、隐式参数进行介绍,使大家明白如何自己实现隐式转换操作。

2. 隐式转换函数

我们在介绍 视图界定的时候,提到 视图界定 的实现 用到了隐式转换,并用 Int 隐式转换成 RichInt 来举例。而下面我们将再举一个例子。

下列赋值如果没有隐式转换的话会报错:
val x:Int = 3.5
添加隐式转换函数后可以实现 Double 类型到 Int 类型的赋值:

implicit def double2Int(x:Double) = x.toInt
val x:Int = 3.5

隐式转换功能十分强大,可以快速地扩展现有类库的功能,例如下面的代码:

package cn.zyb

import java.io.File
import scala.io.Source

class RichFile(val file: File) {
    def read = Source.fromFile(file).getLines().mkString
}

object ImplicitFunction extends App {
    implicit def double2Int(x: Double) = x.toInt
    var x: Int = 3.5

    //隐式函数将java.io.File隐式转换为 RichFile 类
    implicit def file2RichFile(file: File) = new RichFile(file)

    val f = new File("file.log").read
    println(f)
}

3. 隐式转换规则

我们下面会介绍什么时候会发生隐式转换,什么时候不会发生。

3.1 何时触发隐式转换

什么时候会发生隐式转换呢?主要有以下几种情况:

3.1.1 当方法中参数的类型与实际类型不一致时
def f(x: Int) = x + 1
//方法中输入的参数类型与实际类型不一致,会自动寻找 隐式转换函数 double2Int(x: Double)
//如果 double2Int(x: Double) 的参数,和 f(x: Int) 实际输入参数类型一致
//且 double2Int 转换后的返回类型 和 f(x: Int) 函数定义的参数类型一致,则满足匹配
//double 类型会自动调用 此隐式函数,转换为 Int 类型,再进行方法的执行
f(3.14)
3.1.2 当调用类中不存在的方法或成员时

例如上面 RichFile 的代码:

//File类的对象并不存在 read 方法,此时便会发生隐式转换
//将File类转换成 RichFile
val f = new File("file.log").read

3.2 何时不会触发隐式转换

3.2.1 编译器可以正常编译通过时
//下面几条语句,不需要自己定义隐式转换编译就可以通过
//因此它不会发生前面定义的隐式转换
scala> 3.0*2
res0: Double = 6.0

scala> 2*3.0
res1: Double = 6.0

scala> 2*3.7
res2: Double = 7.4
3.2.2 转换存在二义性
//这里定义了一个隐式转换
implicit def file2RichFile(file:File)=new RichFile(file)
//这里又定义了一个隐式转换,目的与前面那个相同
implicit def file2RichFile2(file:File)=new RichFile(file)

//下面这条语句在编译时会出错,报错信息如下(去除了无用信息)
//both method file2RichFile and method file2RichFile2 
//are possible conversion functions from java.io.File to ?{def read: ?}
val f=new File("file.log").read
3.2.3 隐式转换不会嵌套进行

源类型到目标类型的转换只会进行一次
下面看一下不完全代码:

implicit def richFile2RichFileAnother(richFile: RichFile) = new RichFileAnother(richFile)

//RichFileAnother类,里面定义了read2方法
class RichFileAnother(val richFile: RichFile) {
    def read2 = file.read
}

//隐式转换不会多次进行,下面的语句会报错
//不能期望会发生 File 到 RichFile,然后 RifchFile 到 RichFileAnthoer 的转换
val f = new File("file.log").read2

4. 隐式参数

我们在 关于 Scala 界定、隐式转换的一些知识(五)——上下文界定 中,说到 上下文界定用到了 隐式参数。如果给函数定义隐式参数的话,则在使用时可以不带参数

class Pair[T: Ordering](val first: T, val second: T) {
    //smaller 方法中有一个隐式参数,该隐式参数类型为 Ordering[T]
    def smaller(implicit ord: Ordering[T]) = {
        if(ord.compare(first, second) > 0) first else second
    }
}

implicit val p1 = new PersonOrdering
//不给函数指定参数,此时会查找一个隐式值,该隐式值类型为 Ordering[Person]
//根据上下文界定的要求,p1 正好满足要求
//因此它会作为 smaller 的隐式参数传入,从而调用 ord.compare(first,second) 方法进行比较
val p = new Pair(Person("123"), Person("456"))
//调用时并没有传入隐式参数
p.smaller

5. 隐式参数中的隐式转换

前一讲中,我们提到函数中如果存在隐式参数,在使用该函数时 编译器会自动帮我们搜索相应的隐式值,并将该隐式值作为函数的参数
这里面其实没有涉及到隐式转换,本节将演示如何利用隐式参数进行隐式转换,下面的代码给定的是一个普通的比较函数:

object ImplicitParameter extends App {
    //下面的代码不能编译通过,这里面泛型T没有具体指定
    //它不能直接使用 < 符号进行比较
    def compare[T](first: T, second: T) = {
        if (first < second) first else second
    }
}

面的代码要想使其编译通过,可以为 T 指定其上界为 Ordered[T]

object ImplicitParameter extends App {
    def compare[T <: Ordered[T]](first: T, second: T) = {
        if (first < second) first else second
    }
}

这是一种解决方案,我们还有一种解决方案就是通过隐式参数的隐式转换来实现,代码如下:

object ImplicitParameter extends App {
    def compare[T](first: T, second: T)(implicit order: T => Ordered[T]) = {
        if (first < second) first else second
    }
}

6. 隐式转换问题梳理

6.1 多次隐式转换问题

3.2.3 隐式转换不会嵌套进行 中我们提到,源类型到目标类型的转换只会进行一次,并不是说不存在多次隐式转换,在一般的方法调用过程中可能会出现多次隐式转换,例如:

class ClassA {
    override def toString() = "This is Class A"
}
class ClassB {
    override def toString() = "This is Class B"
}
class ClassC {
    override def toString() = "This is  ClassC"
    def printC(c: ClassC) = println(c)
}
class ClassD

object ImplicitWhole extends App {
    implicit def B2C(b: ClassB) = {
        println("B2C")
        new ClassC
    }
    implicit def D2C(d: ClassD) = {
        println("D2C")
        new ClassC
    }
    //下面的代码会进行两次隐式转换
    //因为ClassD中并没有printC方法
    //因为它会隐式转换为ClassC(这是第一次,D2C)
    //然后调用printC方法
    //但是printC方法只接受ClassC类型的参数
    //然而传入的参数类型是ClassB
    //类型不匹配,从而又发生了一次隐式转地换(这是第二次,B2C)
    //从而最终实现了方法的调用
    new ClassD().printC(new ClassB)
}

还有一种情况也会发生多次隐式转换,如果给函数定义了隐式参数,在实际执行过程中可能会发生多次隐式转换,代码如下:

object Main extends App {
    class PrintOps() {
        def print(implicit i: Int) = println(i);
    }

    implicit def str2PrintOps(s: String) = {
        println("str2PrintOps")
        new PrintOps
    }

    implicit def str2int(implicit s: String): Int = {
        println("str2int")
        Integer.parseInt(s)
    }

    implicit def getString = {
        println("getString")
        "123"
    }
    //下面的代码会发生三次隐式转换
    //首先编译器发现String类型是没有print方法的
    //尝试隐式转换,利用str2PrintOps方法将String
    //转换成PrintOps(第一次)
    //然后调用print方法,但print方法接受整型的隐式参数
    //此时编译器会搜索隐式值,但程序里面没有给定,此时
    //编译器会尝试调用 str2int方法进行隐式转换,但该方法
    //又接受一个implicit String类型参数,编译器又会尝试
    //查找一个对应的隐式值,此时又没有,因此编译器会尝试调用
    //getString方法对应的字符串(这是第二次隐式转换,
    //获取一个字符串,从无到有的过程)
    //得到该字符串后,再调用str2int方法将String类型字符串
    //转换成Int类型(这是第三次隐式转换)
    "a".print
}

6.2 要不要用隐式转换的问题

从上述代码中可以看到,隐式转换功能很强大,但同时也带来了程序复杂性性问题,在一个程序中如果大量运用隐式转换,特别是涉及到多次隐式转换时,会使代码理解起来变得比较困难,那到底要不要用隐式转换呢?下面给出我自己开发实践中的部分总结,供大家参考:

  1. 即使你能轻松驾驭 scala 语言中的隐式转换,能不用隐式转换就尽量不用
  2. 如果一定要用,在涉及多次隐式转换时,必须要说服自己这样做的合理性
  3. 如果只是炫耀自己的scala语言能力,请大胆使用

参考

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

推荐阅读更多精彩内容