Scala 系列(十三)—— 隐式转换和隐式参数

一、隐式转换 1.1 使用隐式转换

隐式转换指的是以implicit关键字声明带有单个参数的转换函数,它将值从一种类型转换为另一种类型,以便使用之前类型所没有的功能。示例如下:

// 普通人 class Person(val name: String) // 雷神 class Thor(val name: String) { // 正常情况下只有雷神才能举起雷神之锤 def hammer(): Unit = { println(name + "举起雷神之锤") } } object Thor extends App { // 定义隐式转换方法 将普通人转换为雷神 通常建议方法名使用source2Target,即:被转换对象To转换对象 implicit def person2Thor(p: Person): Thor = new Thor(p.name) // 这样普通人也能举起雷神之锤 new Person("普通人").hammer() } 输出: 普通人举起雷神之锤 1.2 隐式转换规则

并不是你使用implicit转换后,隐式转换就一定会发生,比如上面如果不调用hammer()方法的时候,普通人就还是普通人。通常程序会在以下情况下尝试执行隐式转换:

当对象访问一个不存在的成员时,即调用的方法不存在或者访问的成员变量不存在;

当对象调用某个方法,该方法存在,但是方法的声明参数与传入参数不匹配时。

而在以下三种情况下编译器不会尝试执行隐式转换:

如果代码能够在不使用隐式转换的前提下通过编译,则不会使用隐式转换;

编译器不会尝试同时执行多个转换,比如convert1(convert2(a))*b;

转换存在二义性,也不会发生转换。

这里首先解释一下二义性,上面的代码进行如下修改,由于两个隐式转换都是生效的,所以就存在了二义性:

//两个隐式转换都是有效的 implicit def person2Thor(p: Person): Thor = new Thor(p.name) implicit def person2Thor2(p: Person): Thor = new Thor(p.name) // 此时下面这段语句无法通过编译 new Person("普通人").hammer()

其次再解释一下多个转换的问题:

class ClassA { override def toString = "This is Class A" } class ClassB { override def toString = "This is Class B" def printB(b: ClassB): Unit = println(b) } class ClassC class ClassD object ImplicitTest extends App { implicit def A2B(a: ClassA): ClassB = { println("A2B") new ClassB } implicit def C2B(c: ClassC): ClassB = { println("C2B") new ClassB } implicit def D2C(d: ClassD): ClassC = { println("D2C") new ClassC } // 这行代码无法通过编译,因为要调用到printB方法,需要执行两次转换C2B(D2C(ClassD)) new ClassD().printB(new ClassA) /* * 下面的这一行代码虽然也进行了两次隐式转换,但是两次的转换对象并不是一个对象,所以它是生效的: * 转换流程如下: * 1. ClassC中并没有printB方法,因此隐式转换为ClassB,然后调用printB方法; * 2. 但是printB参数类型为ClassB,然而传入的参数类型是ClassA,所以需要将参数ClassA转换为ClassB,这是第二次; * 即: C2B(ClassC) -> ClassB.printB(ClassA) -> ClassB.printB(A2B(ClassA)) -> ClassB.printB(ClassB) * 转换过程1的对象是ClassC,而转换过程2的转换对象是ClassA,所以虽然是一行代码两次转换,但是仍然是有效转换 */ new ClassC().printB(new ClassA) } // 输出: C2B A2B This is Class B 1.3 引入隐式转换

隐式转换的可以定义在以下三个地方:

定义在原类型的伴生对象中;

直接定义在执行代码的上下文作用域中;

统一定义在一个文件中,在使用时候导入。

上面我们使用的方法相当于直接定义在执行代码的作用域中,下面分别给出其他两种定义的代码示例:

定义在原类型的伴生对象中

class Person(val name: String) // 在伴生对象中定义隐式转换函数 object Person{ implicit def person2Thor(p: Person): Thor = new Thor(p.name) } class Thor(val name: String) { def hammer(): Unit = { println(name + "举起雷神之锤") } } // 使用示例 object ScalaApp extends App { new Person("普通人").hammer() }

定义在一个公共的对象中

object Convert { implicit def person2Thor(p: Person): Thor = new Thor(p.name) } // 导入Convert下所有的隐式转换函数 import com.heibaiying.Convert._ object ScalaApp extends App { new Person("普通人").hammer() }

注:Scala自身的隐式转换函数大部分定义在Predef.scala中,你可以打开源文件查看,也可以在Scala交互式命令行中采用:implicit -v查看全部隐式转换函数。


二、隐式参数 2.1 使用隐式参数

在定义函数或方法时可以使用标记为implicit的参数,这种情况下,编译器将会查找默认值,提供给函数调用。

// 定义分隔符类 class Delimiters(val left: String, val right: String) object ScalaApp extends App { // 进行格式化输出 def formatted(context: String)(implicit deli: Delimiters): Unit = { println(deli.left + context + deli.right) } // 定义一个隐式默认值 使用左右中括号作为分隔符 implicit val bracket = new Delimiters("(", ")") formatted("this is context") // 输出: (this is context) }

关于隐式参数,有两点需要注意:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwpxjd.html