我们经常引入第三方库,但当我们想要扩展新功能的时候通常是很不方便的,因为我们不能直接修改其代码。scala提供了隐式转换机制和隐式参数帮我们解决诸如这样的问题。
Scala中的隐式转换是一种非常强大的代码查找机制。当函数、构造器调用缺少参数或者某一实例调用了其他类型的方法导致编译不通过时,编译器会尝试搜索一些特定的区域,尝试使编译通过。
通过隐式转换,程序员可以在编写Scala程序时故意漏掉一些信息,让编译器去尝试在编译期间自动推导出这些信息来,这种特性可以极大的减少代码量,忽略那些冗长,过于细节的代码。
简单说,隐式转换就是:当Scala编译器进行类型匹配时,如果找不到合适的候选,那么隐式转化提供了另外一种途径来告诉编译器如何将当前的类型转换成预期类型。
如果没有第5条,我将会有个疑问?假设我们第一次隐式转换把int变成String,隐式转换是
implicit def int2String(x : Int) = x.toString
一个Int=>String的隐式转换,import后,Int会被编译器替换为String
会替换,那么如果没第二条规则,我们就惨了,从引入隐式转换开始,到我们程序结束,都是Int会被编译器替换为String,也就是说没有String类型了,int类型有俩了,一个是"10",一个是10,想想多么可怕。所以发生一次是比较好的。想多次使用的时候我们可以多次引用。
这一部分的规则有些复杂,根据《Scala In Depth》所描述的,顶层的搜索逻辑是:
真正复杂的地方是什么叫一个类型的隐式作用域?一个类型的隐式作用域指的是“与该类型相关联的类型”的所有的伴生对象。
OK,那什么叫作“与一个类型相关联的类型”? 定义如下:
隐式转换有新旧两种定义方法,旧的定义方法指是的“implict def”形式,这是Scala 2.10版本之前的写法,在Scala 2.10版本之后,Scala推出了“隐式类”用来替换旧的隐式转换语法,因为“隐式类”是一种更加安全的方式,对被转换的类型来说,它的作用域更加清晰可控。
接下来我们通过实例来了解这两种隐式转换的写法。前文提到,隐式转换最为基本的使用场景是:将某一类型转换成预期类型,所以我们下面的例子就以最这种最简单的场景来演示,它们都实现了:将一个String类型的变量隐式转换为Int类型:
简单查找 看列子
class Implicits {implicit val content = "Java Hadoop"
}
object Implicits {implicit val content = "Scala Spark"
}
object ImplicitsAdvanced {def main(args: Array[String]): Unit = {def printContent(implicit content: String) = println(content) implicit val content = "I love spark"import Implicits._ printContent
}
printContent 的隐式参数是String类型,所以会找String类型的隐式参数,
上面例子上有三个可能,“Java Hadoop”, “Scala Spark”, “I love spark”
结果是
I love spark
即它会先在main这个大括号里找满足要求的String类型隐式参数,找到就用,优先级最高。
只有找不到了才会去导入的类里找,把implicit val content = "I love spark"注释掉后
结果
Scala Spark
Scala Spark 它要找的是伴生对象里的隐式参数,而非类里面的隐式参数!
如果再加上一个隐式参数如下
object ImplicitsMsg {implicit val content = "Kafak Zookeeper"
}
导入时这样导入
import ImplicitsMsg.content
结果是
Kafak Zookeeper
说明都是导入的情况下,优先选择具体的,通配符导入的优化级较低。
使用方式:
1.将方法或变量标记为implicit
2.将方法的参数列表标记为implicit
3.将类标记为implicit
Scala支持两种形式的隐式转换:
隐式值:用于给方法提供参数
隐式视图:用于类型间转换或使针对某类型的方法能调用成功
package source.mscalaobject ImplicitDefDemo {object MyImplicitTypeConversion {implicit def strToInt(str: String) = str.toInt}def main(args: Array[String]) {//compile error!val max = math.max("1", 2);/*import MyImplicitTypeConversion.strToIntval max = math.max("1", 2);*/println(max)}
}
运行直接报错
Error:(11, 20) overloaded method value max with alternatives:(x: Double,y: Double)Double
然后修改
package source.mscalaobject ImplicitDefDemo {object MyImplicitTypeConversion {implicit def strToInt(str: String) = str.toInt}def main(args: Array[String]) {//compile error!// val max = math.max("1", 2);import MyImplicitTypeConversion.strToIntval max = math.max("1", 2);println(max)}
}
运行结果正确
2
Process finished with exit code 0
这里有疑问?为什么加入import MyImplicitTypeConversion.strToInt这一句代码就可以了呢?
假设不引入隐式值试试,( 其中试试了print(person()) 这样编译都会报错)
package source.mscalaobject ImplicitDefDemo {def person(implicit name : String) = namedef main(args: Array[String]) {print(person)}
}
直接报错
Error:(6, 11) could not find implicit value for parameter name: Stringprint(person)
Error:(6, 11) not enough arguments for method person: (implicit name: String)String.
Unspecified value parameter name.print(person)
然后修改,引入隐式值
package source.mscalaobject ImplicitDefDemo {def person(implicit name : String) = namedef main(args: Array[String]) {implicit val p = "mobin" //p被称为隐式值print(person)}
}
运行结果正确
mobin
Process finished with exit code 0
假设我如果传入了值呢?这时候使用的是哪种在呢?
package source.mscalaobject ImplicitDefDemo {def person(implicit name : String) = namedef main(args: Array[String]) {implicit val p = "mobin" //p被称为隐式值print(person("aa"))}
}
运行结果是我们传入的值,隐式转换没起作用,这里符合了 隐式转换的规则中的第一条和第六条
aa
Process finished with exit code 0
如果我们定义了两个隐式值呢?
package source.mscalaobject ImplicitDefDemo {def person(implicit name : String) = namedef main(args: Array[String]) {implicit val p = "mobin" //p被称为隐式值implicit val myp2 = "i am two" //myp2被称为隐式值print(person)}
}
会报错
Error:(9, 11) ambiguous implicit values:both value myp2 of type Stringand value p of type Stringmatch expected type Stringprint(person)Error:(9, 11) could not find implicit value for parameter name: Stringprint(person)Error:(9, 11) not enough arguments for method person: (implicit name: String)String.
Unspecified value parameter name.print(person)
这里的意思是,print(person)因为你没传入参数,而person(implicit name : String) 需要的隐式参数是String类型的,所以去找隐式值,然后根据搜索范围,根据第一条 在当前作用域下查找。这种情形又分两种情况,一个是在当前作用域显示声明的implicit元素,另一个通过import导入的implicit元素。这里有两个String类型的,因此两个都符合,但是编译器不知道调用哪个啊,所以报错。
假设是不同的类型呢?
package source.mscalaobject ImplicitDefDemo {def person(implicit name : String) = namedef main(args: Array[String]) {implicit val p:String = "mobin" //p被称为隐式值 String类型implicit val myp2:Int = 3 //myp2被称为隐式值 int类型print(person)}
}
运行结果正确,虽然在一个搜索范围里有两个隐式值,但是类型不符合
mobin
Process finished with exit code 0
隐式转换为目标类型:把一种类型自动转换到另一种类型
package source.mscalaobject ImplicitDefDemo {def foo(msg : String) = println(msg)implicit def intToString(x : Int) = x.toStringdef main(args: Array[String]) {foo(10)}
}
如果没有 implicit def intToString(x : Int) = x.toString这个函数,是无法编译的,直接就是错误
package source.mscalaclass SwingType{def wantLearned(sw : String) = println("兔子已经学会了"+sw)
}
object swimming{implicit def learningType(s : AminalType) = {println("i am run ")new SwingType}
}
class AminalType
object AminalType {def main(args: Array[String]) {import source.mscala.swimming._val rabbit = new AminalTyperabbit.wantLearned("breaststroke") //蛙泳}}
如果没有import source.mscala.swimming._,那么无法编译
编译器在rabbit对象调用时发现对象上并没有wantLearning方法,此时编译器就会在作用域范围内查找能使其编译通过的隐式视图,找到learningType方法后,编译器通过隐式转换将对象转换成具有这个方法的对象,之后调用wantLearning方法
运行结果
i am run
兔子已经学会了breaststroke
可以将隐式转换函数定义在伴生对象中,在使用时导入隐式视图到作用域中即可(如例4的learningType函数)
package source.mscalaclass SwingType{def wantLearned(sw : String) = println("兔子已经学会了"+sw)
}package swimmingPage{object swimming{implicit def learningType(s : AminalType) = {println("i am run ")new SwingType}}
}class AminalType
object AminalType {def main(args: Array[String]) {import source.mscala.swimmingPage.swimming._val rabbit = new AminalTyperabbit.wantLearned("breaststroke") //蛙泳}}
运行结果和上面相同,但是有什么区别呢?没感觉多大区别
像intToString,learningType这类的方法就是隐式视图,通常为Int => String的视图,定义的格式如下:
implicit def originalToTarget ( : OriginalType) : TargetType
其通常用在于以两种场合中:
1.如果表达式不符合编译器要求的类型,编译器就会在作用域范围内查找能够使之符合要求的隐式视图。如例2,当要传一个整数类型给要求是字符串类型参数的方法时,在作用域里就必须存在Int => String的隐式视图
2.给定一个选择e.t,如果e的类型里并没有成员t,则编译器会查找能应用到e类型并且返回类型包含成员t的隐式视图。如例3
在scala2.10后提供了隐式类,可以使用implicit声明类,但是需要注意以下几点:
package source.mscalaobject Stringutils {implicit class StringImprovement(val s : String){ //隐式类def increment = s.map(x => (x +1).toChar)}
}
object Main extends App{import source.mscala.Stringutils._println("mobin".increment)
}
编译器在mobin对象调用increment时发现对象上并没有increment方法,此时编译器就会在作用域范围内搜索隐式实体,发现有符合的隐式类可以用来转换成带有increment方法的StringImprovement类,最终调用increment方法。运行结果如下
npcjo
Process finished with exit code 0