一、隐式转换1.1 提出问题先看一段代码引出隐式转换的实际需要——指定某些数据类型的相互转化packagecom.atguigu.scala.conversionobjectScala01{defmain(args:Array[String]):Unit{valnum:Int3.5// ? 错高精度 - 低精度println(num)}}在Scala中将高精度如Double赋值给低精度如Int时会报错。如何优雅地解决这个问题1.2 隐式函数基本介绍隐式转换函数是以implicit关键字声明的带有单个参数的函数。这种函数将会自动应用将值从一种类型转换为另一种类型。1.3 隐式函数快速入门使用隐式函数可以优雅地解决数据类型转换问题implicitdeff1(d:Double):Int{d.toInt}// Double 是输入类型Int 是转换后的类型1.4 隐式函数的底层工作原理defmain(args:Array[String]):Unit{implicitdeff1(d:Double):Int{d.toInt}implicitdeff2(l:Long):Int{l.toInt}valnum:Int3.5// 编译器自动调用 f1(3.5)println(num)// 输出: 3valnum2:Int4.5// 编译器自动调用 f1(4.5)println(num2)// 输出: 3valnum3:Int20l// 编译器自动调用 f2(20l)}底层实现编译器在编译时自动查找并调用合适的隐式转换函数。1.5 隐式转换的注意事项和细节隐式转换函数的函数名可以是任意的隐式转换与函数名称无关只与函数签名函数参数类型和返回值类型有关。隐式函数可以有多个即隐式函数列表但是需要保证在当前环境下只有一个隐式函数能被识别。// 在当前环境中不能存在满足条件的多个隐式函数implicitdefa(d:Double)d.toIntimplicitdefb(d:Double)d.toIntvali1:Int3.5// (X) 在转换时识别出有两个方法可以被使用// 就不确定调用哪一个所以出错println(i1)二、隐式转换丰富类库功能2.1 基本介绍如果需要为一个类增加一个方法可以通过隐式转换来实现动态增加功能。比如想为MySQL类增加一个delete方法。2.2 分析解决方案在当前程序中如果想要给MySQL类增加功能是非常简单的但是在实际项目中如果想要增加新的功能就会需要改变源代码这是很难接受的。而且违背了软件开发的OCP开发原则开闭原则 Open Close Principle。在这种情况下可以通过隐式转换函数给类动态添加功能。2.3 快速入门案例使用隐式转换方式动态地给MySQL类增加delete方法classMySQL{definsert():Unit{println(insert)}}classDB{defdelete():Unit{println(delete)}}// 隐式转换函数implicitdefaddDelete(mysql:MySQL):DB{newDB}valmysqlnewMySQL mysql.insert()mysql.delete()// ? 通过隐式转换MySQL对象可以调用DB的delete方法底层原理编译器发现MySQL没有delete方法自动查找隐式转换函数将MySQL转换为DB类型。三、隐式值3.1 基本介绍隐式值也叫隐式变量将某个形参变量标记为implicit所以编译器会在方法省略隐式参数的情况下去搜索作用域内的隐式值作为缺省参数。3.2 应用案例implicitvalstr1:Stringjackdefhello(implicitname:String):Unit{println(name hello)}hello// 调用不带()编译器自动传入隐式值str1// 输出: jack hello3.3 课堂测试题题1下面的代码是否正确objectImplicitVal{defmain(args:Array[String]):Unit{implicitvalstr1:Stringjack// 隐式值defhello(implicitname:String):Unit{// hello$1println(name hello)}defhello():Unit{// hello$2println(xxxx)}hello// hello$1(str1) 使用隐式值不要带()}}答案正确调用的是带隐式参数的hello方法传入隐式值jack。题2objectScala03{defmain(args:Array[String]):Unit{// 隐式变量值implicitvalname:StringScaladefhello(implicitcontent:Stringokook):Unit{println(Hello content)}hello// 输出什么内容}}答案输出 “Hello Scala”隐式值优先级高于默认值。题3objectScala03{defmain(args:Array[String]):Unit{// 隐式变量值implicitvalname:Int10// 类型不匹配defhello(implicitcontent:Stringokook):Unit{println(Hello content)}hello// 输出什么内容}}答案输出 “Hello okook”因为隐式值类型不匹配使用默认值。题4objectScala03{defmain(args:Array[String]):Unit{// 隐式变量值implicitvalname:Int10// 类型不匹配defhello(implicitcontent:String):Unit{println(Hello content)}hello// 输出什么}}答案编译错误没有匹配的隐式值也没有默认值。3.4 多个隐式值的优先级objectImplicitVal02{defmain(args:Array[String]):Unit{// 隐式变量值implicitvalname:StringScalaimplicitvalname1:StringWorlddefhello(implicitcontent:Stringjack):Unit{println(Hello content)}hello// 编译错误存在多个同类型隐式值产生歧义}}四、隐式类4.1 基本介绍在Scala 2.10后提供了隐式类可以使用implicit声明类。隐式类非常强大同样可以扩展类的功能比前面使用隐式转换丰富类库功能更加的方便在集合中隐式类会发挥重要的作用。4.2 隐式类的特点隐式类使用有如下几个特点其所带的构造参数有且只能有一个隐式类必须被定义在**类或伴生对象或包对象里即隐式类不能是顶级的**top-level objects隐式类不能是case classcase class在后续介绍样例类作用域内不能有与之相同名称的标识符4.3 应用案例看一个关于隐式类的案例进一步认识隐式类classMySQL1{defsayOk():Unit{println(sayOk)}}defmain(args:Array[String]):Unit{// DB1会对应生成隐式类implicitclassDB1(valm:MySQL1){defaddSuffix():String{m scala}}valmysql1newMySQL1 mysql1.sayOk()// mysql1.addSuffix() DB1$1(mysql1).addSuffix()// DB1$1(mysql1)返回的类型是 ImplicitClass$DB1$2println(mysql1.addSuffix())}底层原理编译器自动生成隐式转换函数将MySQL1对象包装为DB1对象。五、隐式的转换时机隐式转换会在以下两种情况下自动触发当方法中的参数的类型与目标类型不一致时当对象调用所在类中不存在的方法或成员时编译器会自动将对象进行隐式转换根据类型六、隐式解析机制即编译器是如何查找到缺失信息的解析具有以下两种规则首先会在当前代码作用域下查找隐式实体隐式方法、隐式类、隐式对象。一般是这种情况如果第一条规则查找隐式实体失败会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生模块一个隐式实体的类型T它的查找范围如下a) 如果T被定义为T with A with B with C那么A,B,C都是T的部分在T的隐式解析过程中它们的伴生对象都会被搜索。b) 如果T是参数化类型那么类型参数和与类型参数相关联的部分都算作T的部分比如List[String]的隐式搜索会搜索List的伴生对象和String的伴生对象。c) 如果T是一个单例类型p.T即T是属于某个p对象内那么这个p对象也会被搜索。d) 如果T是个类型注入S#T那么S和T都会被搜索。第二种情况范围广且复杂在使用时应当尽量避免出现。七、隐式转换的前提在进行隐式转换时需要遵守两个基本的前提不能存在二义性隐式操作不能嵌套使用// 使用隐式函数来解决// 说明// 1. 在底层会生成一个函数 f1$1...implicitdeff1(d:Double):Int{d.toInt}valnum3:Int5.6// 错误 f1(5.6)注意隐式转换不能嵌套即不能对一个已经经过隐式转换的值再次进行隐式转换。总结本章深入讲解了Scala隐式机制的四大核心内容特性核心要点隐式转换implicit def单参数自动类型转换隐式值implicit val作为方法缺省参数隐式类implicit class构造参数只能有一个不能是顶级隐式解析当前作用域优先类型作用域补充隐式机制是Scala最强大的特性之一合理使用可以大大简化代码但过度使用会降低代码可读性。建议在类型增强、DSL构建、类型类模式等场景下使用。