2、Scala 隐式转换和隐式参数
隐式转换和隐式参数是 Scala 中两个非常强大的功能,利用隐式转换和隐式参数,你可以提 供优雅的类库,
对类库的使用者隐匿掉那些枯燥乏味的细节。
隐式的对类的方法进行增强,丰富现有类库的功能
是指那种以 implicit 关键字声明的带有单个参数的函数。
可以通过::implicit -v 这个命令显示所有做隐式转换的类。
2.1、Scala 隐式转换探讨
现在我们来考虑一个问题: 之前讲过:
1 to 10 其实可以写成 1.to(10)
那其实就是表示:1 是一个 Int 类型的变量,所以证明 Int 类中会有一个 to 的方法 但事实上,我们在 Int 类型中根本就没有寻找 to 方法 那也就是说对一个 Int 类型的变量 1 调用 Int 类型不存在的一个方法,这怎么还能正常运行 呢?
原因就是隐式转换
看一个最简单的隐式转换的例子:
那我们首先来看一下隐式参数:
package com.mazh.scala.day3
object ImplicitParamTest {
// 正常的普通方法
def add(x:Int, y:Int) = x + y
// 柯里化的方法
def add2(x:Int)(y:Int) = x + y
def add3(x:Int)(y:Int = 10) = x + y
// 如果变成下面这种形式:
def add4(x:Int)(implicit y:Int = 10) = x + y
def main(args: Array[String]): Unit = {
println ( add (2,3))
// 不能只传一个参数取使用,必须要传入两个参数
println ( add2 (2)(3))
println ( add 3 (2)())
// 调用带隐式参数的函数
println ( add 4 (2))
}
}
在上面的代码中,可以看出来,如果对 add2 方法的第二个参数,做了隐式声明,发现之前 需要传入两个参数才能执行的方法 add2 就可以只传入一个参数就能执行计算
那有什么应用场景呢? 比如汇率计算!!!!!
object ImplicitParamTest2 {
/**
* 第一个参数是要换算成美元的人民币数目
* 第二个参数是汇率
*/
def rmb(dollar:Double)(implicit rate:Double = 6) = dollar * rate
def main(args: Array[String]): Unit = {
println ( rmb (100))
println ( rmb (100)(7))
// 引入隐式转换值,所以第二个参数被隐式的转换成了 6.66
import MyPredef._
println ( rmb (100))
}
}
object MyPredef{
// 声明一个 Double 类型的隐式转换值
implicit var current_rate :Double = 6.66
}
总结:
1、 隐式转换会首先从全局中寻找,寻找不到,才使用隐式参数
2、 隐式转换只能定义在 object 中
3、 如果隐式转换存在二义性,那么程序会跑错
那现在再来考虑:
对一个 Int 类型的变量 1 调用 Int 类型不存在的一个方法,程序能正常运行得到期待的结果,
没有抛错异常,到底是怎么回事?会不会就是 Int 类型的变量被隐式转换成了另一种包含 to 方法的类型了呢?
1、首先,我们在 RichInt 中发现了 to 方法:
2、查看系统是否为我们自动引入了默认的各种隐式转换: 在 Scala 交互命令行中执行命令:implicits -v
scala> :implicit -v
/* 69 implicit members imported from scala.Predef */
/* 7 inherited from scala */
final implicit class ArrayCharSequence extends CharSequence
final implicit class ArrowAssoc[A] extends AnyVal
final implicit class Ensuring[A] extends AnyVal
final implicit class RichException extends AnyVal
final implicit class SeqCharSequence extends CharSequence
final implicit class StringFormat[A] extends AnyVal
final implicit class any2stringadd[A] extends AnyVal
/* 40 inherited from scala.Predef */
implicit def ArrowAssoc[A](self: A): ArrowAssoc[A]
implicit def Ensuring[A](self: A): Ensuring[A]
implicit def StringFormat[A](self: A): StringFormat[A]
implicit def any2stringadd[A](self: A): any2stringadd[A]
implicit def booleanArrayOps(xs: Array[Boolean]): mutable.ArrayOps[Boolean]
implicit def byteArrayOps(xs: Array[Byte]): mutable.ArrayOps[Byte]
implicit def charArrayOps(xs: Array[Char]): mutable.ArrayOps[Char]
implicit def doubleArrayOps(xs: Array[Double]): mutable.ArrayOps[Double]
implicit def floatArrayOps(xs: Array[Float]): mutable.ArrayOps[Float]
implicit def genericArrayOps[T](xs: Array[T]): mutable.ArrayOps[T]
implicit def intArrayOps(xs: Array[Int]): mutable.ArrayOps[Int]
implicit def longArrayOps(xs: Array[Long]): mutable.ArrayOps[Long]
implicit def refArrayOps[T <: AnyRef](xs: Array[T]): mutable.ArrayOps[T]
implicit def shortArrayOps(xs: Array[Short]): mutable.ArrayOps[Short]
implicit def unitArrayOps(xs: Array[Unit]): mutable.ArrayOps[Unit]
implicit def $conforms[A]: <:<[A,A]
implicit def ArrayCharSequence(__arrayOfChars: Array[Char]): ArrayCharSequence
implicit def Boolean2boolean(x: Boolean): Boolean
implicit def Byte2byte(x: Byte): Byte
implicit def Character2char(x: Character): Char
implicit def Double2double(x: Double): Double
implicit def Float2float(x: Float): Float
implicit def Integer2int(x: Integer): Int
implicit def Long2long(x: Long): Long
implicit def RichException(self: Throwable): RichException
implicit def SeqCharSequence(__sequenceOfChars: IndexedSeq[Char]): SeqCharSequence
implicit def Short2short(x: Short): Short
implicit val StringCanBuildFrom: generic.CanBuildFrom[String,Char,String]
implicit def augmentString(x: String): immutable.StringOps
implicit def boolean2Boolean(x: Boolean): Boolean
implicit def byte2Byte(x: Byte): Byte
implicit def char2Character(x: Char): Character
implicit def double2Double(x: Double): Double
implicit def float2Float(x: Float): Float
implicit def int2Integer(x: Int): Integer
implicit def long2Long(x: Long): Long
implicit def short2Short(x: Short): Short
implicit def tuple2ToZippedOps[T1, T2](x: (T1, T2)): runtime.Tuple2Zipped.Ops[T1,T2]
implicit def tuple3ToZippedOps[T1, T2, T3](x: (T1, T2, T3)):runtime.Tuple3Zipped.Ops[T1,T2,T3]
implicit def unaugmentString(x: immutable.StringOps): String
/* 22 inherited from scala.LowPriorityImplicits */
implicit def genericWrapArray[T](xs: Array[T]): mutable.WrappedArray[T]
implicit def wrapBooleanArray(xs: Array[Boolean]): mutable.WrappedArray[Boolean]
implicit def wrapByteArray(xs: Array[Byte]): mutable.WrappedArray[Byte]
implicit def wrapCharArray(xs: Array[Char]): mutable.WrappedArray[Char]
implicit def wrapDoubleArray(xs: Array[Double]): mutable.WrappedArray[Double]
implicit def wrapFloatArray(xs: Array[Float]): mutable.WrappedArray[Float]
implicit def wrapIntArray(xs: Array[Int]): mutable.WrappedArray[Int]
implicit def wrapLongArray(xs: Array[Long]): mutable.WrappedArray[Long]
implicit def wrapRefArray[T <: AnyRef](xs: Array[T]): mutable.WrappedArray[T]
implicit def wrapShortArray(xs: Array[Short]): mutable.WrappedArray[Short]
implicit def wrapUnitArray(xs: Array[Unit]): mutable.WrappedArray[Unit]
implicit def booleanWrapper(x: Boolean): runtime.RichBoolean
implicit def byteWrapper(x: Byte): runtime.RichByte
implicit def charWrapper(c: Char): runtime.RichChar
implicit def doubleWrapper(x: Double): runtime.RichDouble
implicit def floatWrapper(x: Float): runtime.RichFloat
implicit def intWrapper(x: Int): runtime.RichInt
implicit def longWrapper(x: Long): runtime.RichLong
implicit def shortWrapper(x: Short): runtime.RichShort
implicit def unwrapString(ws: immutable.WrappedString): String
implicit def wrapString(s: String): immutable.WrappedString
通过观察发现,scala 会默认给我们引入 scala 中的 Predef.scala 中的所有隐式转换
https://www.scala-lang.org/api/2.11.8/#scala.Predef$
最后在倒数第五行发现,有一个隐式方法能够把 Int 类型的变量转换成 runtime.RichInt 变量 符合我们的预期
最终解释:
当调用了:1 to 10
其实是调用了:1.to(10)
但是:Int 中没有 to 方法 所以:去寻找引入的隐式转换中有没有能把 Int 类型转换成能执行 to 方法的类型
果然:在系统引入的转换中发现:implicit def intWrapper(x: Int): runtime.RichInt
所以:最终 int 类型的 1 就被转换成了 RichInt 类型的变量
验证:RichInt 中确实存在 to 方法
最后:顺理成章的调用 RichInt(1).to(10)生成返回结果
结论:神奇但又合理
2.2、隐式转换的发生时机
到底在什么时候触发隐式转换呢?
2.2.1、时机一:当调用某个对象不存在的方法时
当一个对象去调用某个方法,但是这个对象并不具备这个方法。这个时候会触发隐式转换, 会把这个对象(偷偷的)隐式转换为具有这个方法的那个对象。这就和刚才解释的为什么 Int 类型没有 to 方法还是能够调用 to 方法,因为 Int 类型的变量 1 在调用 to 方法的时候,被隐 式转换成了 RichInt 的对象
再次演示一个案例:
object ImplicitTest2 {
def main(args: Array[String]): Unit = {
import FileImplicit._
val file = new File("c:\\words.txt")
// file 对象是没有 readAll 方法的, 那么在调用一个不存在的方法的时候
// scala 会查看是否有隐式转换能把 file 对象转换成具有 readAll 方法对象
val allText = file.readAll()
println (allText)
}
}
class RichFile(f:File){
def readAll():String = {
Source. fromFile (f).mkString
}
}
object FileImplicit{
implicit def file2RichFile(f:File):RichFile = new RichFile(f)
}
2.2.2、时机二:当方法参数类型不匹配时
正常情况下,我们在编写代码,如果去调用某个方法,确实这个方法也存在,要是传入的参 数类型不匹配,程序会抛错,这是再正常不过的事情了。
可是 Scala 却在为我们做努力,努力帮助我们把这个方法调用执行起来。因为隐式转换的存 在 第一个案例:
object Demo009_Implicit_Type {
def main(args: Array[String]): Unit = {
// 定义一个隐式转换,能够、把一个 double类型的数编程 int类型。
implicit def double2Int(a:Double) = a.toInt
// 定义三个方法
def sum1(x:Int, y:Int) = x + y
def sum2(x:Int, y:Double) = x + y
def sum3(x:Double, y:Double) = x + y
// 使用
println(sum1(1, 2.0)) // 触发隐式转换
println(sum2(1, 2)) // 触发隐式转换
println(sum3(1,2)) // 触发隐式转换
}
}
第二个案例:
package com.mazh.scala.day3
// 特殊人群
class SpecialPerson(var name:String)
// 特殊人群之一
class Young(name:String)
// 特殊人群之二
class Older(name:String)
// 正常人群之一
class Worker(var name:String)
// 正常人群之二
class Adult(var name:String)
class TicketHouse{
def buyTicket(p:SpecialPerson): Unit ={
println (p.name+"票给你!!爽去吧!!");
}
}
object ObjectImplicit{
implicit def object2special(obj:AneyRef):SpecialPerson={
// 这么 写的原因是给大家演示这三个 方法 的使用。其实 有 更简单的实现
if(obj.getClass == classOf [Young]){
val young = obj.asInstanceOf[Young]
new SpecialPerson(young.name)
}else if(obj.getClass == classOf [Older]){
val older = obj.asInstanceOf[Older]
new SpecialPerson(older.name)
}else{
new SpecialPerson("NULL")
}
}
}
object ImplicitTest3 {
def main(args: Array[String]): Unit = {
val ticketHouse = new TicketHouse()
val young = new Young("Young")
val older = new Older("Older")
val worker = new Worker("Worker")
val adult = new Worker("Adult")
import ObjectImplicit._
// ticketHouse.buyTicke t(worker) // 报错
// ticketHouse.buyTicket(adult) // 报错
ticketHouse.buyTicket(young)
ticketHouse.buyTicket(older)
}
}
2.3、隐式转换忠告
下面给出我自己开发实践中的部分总结,供大家参考:
1、即使你能轻松驾驭 Scala 语言中的隐式转换,能不用隐式转换就尽量不用
2、如果一定要用,在涉及多次隐式转换时,必须要说服自己这样做的合理性
3、如果只是炫耀自己的 Scala 编程能力,请大胆使用