scala这写的都是啥?一篇文章搞清隐式转换
前言
我们都知道scala以简洁著称,怎么简单怎么来。对于scala的简洁语法,阔以说熟悉的人爱死scala,不熟悉的人被scala折磨死。对于scala这种语言,习惯了java的同学经常听到一些新的名词,不可思议而且神神秘秘,对,没错,说的就是你——隐式转换,掖着藏着,快让我窥探一下你的秘密。
常规问题:隐式转换是什么鬼
在这个地方,我们暂且先不谈隐式转换是什么,先瞄准scala的类型转换,对于scala的Byte,Short,Int和Long类型,如果我们Byte -> Short -> Int -> Long这样转换的话,那么scala就自动帮我们实现了。除此之外,我们常见的还有多态语法中的类型转换,比如 子类 -> 父类 -> 特质。这些都是scala在默认情况下就支持的自动类型转换,是隐式转换的一种,但如果我们光说到这儿就结束了,那scala也没啥好学的了。
我们以上所说的自动转换,都是带有关系的,例如继承的关系或者精度的关系。考虑一种情况,如果我们想要在两个无关类型中进行转换,那么scala中的隐式转换能实现吗。
这就需要我们自定义转换规则,让其实现隐式转换。
如何实现隐式转换
我们自定义的隐式转换,是需要函数实现的,即隐式转换函数,隐式转换函数是以implicit关键字声明的带有单个参数的函数,这种函数将会自动应用,将值从一种类型转换到另一种类型。
快速入门
show me the code:
def main(args: Array[String]): Unit = {
// implicit 修饰的隐式函数
implicit def transform(d:Double):Int={
d.toInt
}
val i:Int = 5.0
println(i)
}
implicit关键字修饰的函数为隐式函数,在这个例子中,先看i,类型定义为Int类型,但是赋值5.0,这里类型存在不同,编译器在无法自动转换该两种类型的时候,查找自定义转换规则,即查找implicit关键字,发现该处隐式转换函数的类型正好符合,于是进行了隐式转换。
如果我们把参数的类型或返回值类型进行改变,转换是否会成功呢?
def main(args: Array[String]): Unit = {
//返回值 从Int改为Float
implicit def transform(d:Double):Float={
d.toInt
}
val i:Int = 5.0
println(i)
}
运行我们会发现,我们无法实现转换,编译器会报错。
type mismatch;
found : Double(5.0)
required: Int
val i:Int = 5.0
改变参数名和参数类型的例子可自己尝试
从上面的例子可以看出,隐式转换函数的函数名可以是任意的,隐式转换只与函数的参数类型和返回值类型有关,与函数名无关。
我们接着再来看,如果连续写两个隐式函数,参数和返回值类型相同,函数名不同,会如何呢?
def main(args: Array[String]): Unit = {
implicit def transform(d:Double):Int={
d.toInt
}
implicit def transform1(d:Double):Int={
d.toInt
}
val i:Int = 5.0
println(i)
}
执行,发现会出现如下错误
Note that implicit conversions are not applicable because they are ambiguous:
both method transform1 of type (d: Double)Int
and method transform of type (d: Double)Int
are possible conversion functions from Double(5.0) to Int
val i:Int = 5.0
表示,这里有多个相同作用的隐私转换函数,编译器无法判断用哪个合适。因此我们应当保证,隐式函数可以有多个,但同一作用域不能有多个相同类型的转换规则。
小结:
- 隐式转换函数是以implicit关键字声明的带有单个参数的函数
- 隐式转换只与函数的参数类型和返回值类型有关,与函数名无关
- 同一作用域不能有多个相同类型的转换规则
强大的扩展功能
在当前的程序中,如果想要给一个已有的类增加新方法是非常简单的,但是实际项目中,如果想要增加新方法,就需要修改源代码,一旦修改源代码,就可能违背了OCP开闭原则,这是难以接受的,因此在这种情况下,可以通过隐式转换函数给类动态增加功能。
还是拿个栗子🌰来代替干巴巴的解释:
现在我们开发了一个Mysql的类,里面只包含了select方法,这个已经提交到了代码库中,不管什么原因吧,我们无法对Mysql类源代码进行修改。
class Mysql {
def select(): Unit = {
//此处省去很多行代码
}
}
def main(args: Array[String]): Unit = {
val mysql = new Mysql()
mysql.select()
}
在后来的需求迭代中,需要为mysql增加一个delete方法,但是不能修改源代码,我们此刻就可以通过隐式转换为其扩展功能
class Operater {
def delete(): Unit = {
//此处省去很多行代码
}
}
def main(args: Array[String]): Unit = {
val mysql = new Mysql()
mysql.select()
implicit def transfrom(mysql: Mysql): Operater = {
new Operater()
}
mysql.delete()
}
有哪些隐式转换
隐式方法
我们上文所用的入门案例,即是隐式方法,此处不再赘述。
隐式值
隐式值也叫隐式变量,将某个参数标记为implicit,编译器会在方法调用省略参数的情况下搜索作用域内的隐式值作为默认参数。不得不吐槽,这句话乱七八糟的,但是没办法,scala这个语法就是这么有点乱。
应用案例:
def main(args: Array[String]): Unit = {
implicit val nameTr:String = "关羽"
def function(implicit name:String):Unit={
println(name)
}
function("赵云")
function
}
输出:
赵云
关羽
按照定义来解释,当方法调用传参的时候,scala使用传递的参数,当方法调用无参的时候,scala使用隐式转换,将nameTr转换到name。需要注意的是,nameTr和name的类型必须一致才能实现转换
隐式类
在scala2.0之后提供了隐式类,可以使用implict声明类,隐式类同样可以扩展类的功能,比前面使用隐式方法转换丰富类库功能更加方便。
隐式类使用有如下几个特点:
- 类的构造函数有且只能有一个,这个参数即是需要转换的对象
- 隐式类只能定义在类、伴生对象或包对象内部,不能是顶级的
- 隐式类不能是case class
由于隐式类比隐式方法转换丰富类库功能更加方便,因此我们通过改造前面的Mysql的例子进行对比,我们可以通过只定义一个隐式类就可以实现功能拓展。
class Mysql {
def select(): Unit = {
}
}
def main(args: Array[String]): Unit = {
val mysql = new Mysql()
mysql.select()
//注意此处构造方法,传入了Mysql的对象
implicit class Operater(mysql1:Mysql) {
def delete(): Unit = {
}
}
mysql.delete()
}
最后
学习scala不易,这篇文章揭开了隐式转换神秘的面纱,从隐式转换的介绍,到使用,到分类,尽量用通俗的语言和详细的demo案例解释清楚,希望能给不熟悉scala隐式转换的小伙伴一点帮助。