Scala之隐式转换
概述
简单说,隐式转换就是:当Scala编译器进行类型匹配时,如果找不到合适的候选,那么隐式转化提供了另外一种途径来告诉编译器如何将当前的类型转换成预期类型。
隐式转换有四种常见的使用场景:
将某一类型转换成预期类型
类型增强与扩展
模拟新的语法
类型类
语法
隐式转换有新旧两种定义方法,旧的定义方法指是的“implict def”形式,这是Scala 2.10版本之前的写法,在Scala 2.10版本之后,Scala推出了“隐式类”用来替换旧的隐式转换语法,因为“隐式类”是一种更加安全的方式,对被转换的类型来说,它的作用域更加清晰可控。
接下来我们通过实例来了解这两种隐式转换的写法。前文提到,隐式转换最为基本的使用场景是:将某一类型转换成预期类型,所以我们下面的例子就以最这种最简单的场景来演示,它们都实现了:将一个String类型的变量隐式转换为Int类型:
“implict def”形式的隐式转换
package com.github.scala.myimplicit
/**
* A demo about scala implicit type conversion.
* @author Laurence Geng
*/
object 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.strToInt
val max = math.max("1", 2);
println(max)
}
}
“隐式类”形式的隐式转换
package com.github.scala.myimplicit
/**
* A demo about scala implicit type conversion.
* @author Laurence Geng
*/
object ImplicitClassDemo {
implicit class MyImplicitTypeConversion(val str: String){
def strToInt = str.toInt
}
def main(args: Array[String]) {
//compile error!
//val max = math.max("1", 2);
import com.github.scala.myimplicit.ImplicitDefDemo.MyImplicitTypeConversion._
val max = math.max("1", 2);
println(max)
}
}
隐式类有如下几个限制:
They must be defined inside of another trait/class/object.
They may only take one non-implicit argument in their constructor.
There may not be any method, member or object in scope with the same name as the implicit class.
Note: This means an implicit class cannot be a case class.
隐式类与旧的隐式转换的语法(implicit def)是有细微的不同的,隐式类的运作方式是:隐式类的主构造函数只能有一个参数(有两个以上并不会报错,但是这个隐式类永远不会被编译器作为隐式类在隐式转化中使用),且这个参数的类型就是将要被转换的目标类型。从语义上这很自然:这个隐式转换类将包裹目标类型,隐式类的所有方法都会自动“附加”到目标类型上。
应用场景
转换成预期类型
对于这种使用场景实际上并不多见,实际意义也没有那么大。前文的代码就是这样这一场景的一个示例。
类型增强与扩展
真正让隐式转换大放异彩的是“类型增强”。这方面的示例是非常多的。
案例一:ArrayOps对Array的类型增强
一个典型案例是:Scala对Array对象进行的隐式转换。我们知道,Scala通过Predef声明了针对Array类型的两个隐式转换:一个是到ArrayOps的隐式转化,另一个是到WrappedArray的隐式转换。以前者为例,它为Array对象“添加”了大量的操作,这是通过隐式转换来”通明“的对一个类进行增强的典型案例!以下是Scala API文档中对这一技术细节的说明:
Two implicit conversions exist in scala.Predef that are frequently applied to arrays: a conversion to scala.collection.mutable.ArrayOps (shown on line 4 of the example above) and a conversion to scala.collection.mutable.WrappedArray (a subtype of scala.collection.Seq). Both types make available many of the standard operations found in the Scala collections API. The conversion to ArrayOps is temporary, as all operations defined on ArrayOps return an Array, while the conversion to WrappedArray is permanent as all operations return a WrappedArray.
案例二:Spark中PairRDDFunctions对RDD的类型增强
如果你看一下Spark中的RDD以及它的子类是没有groupByKey, reduceByKey以及join这一类基于key-value元组的操作的,但是在你使用RDD时,这些操作是实实在在存在的,Spark正是通过隐式转换将一个RDD转换成了PairRDDFunctions, 这个动作是这样发生的:
首先在RDD的伴随对象中声明了从RDD到PairRDDFunctions的隐式转换:
然后在SparkContext中import了RDD的所有东西,使隐式转换生效。
模拟新的语法
这也是非常酷的一种应用场景。一个典型的应用场景就是Map中用于创建key-value元组的->符号,它就是一个隐式转换的产物。->不是 scala 本身的语法,而是类型 ArrowAssoc 的一个方法。这个类型定义在包 Scala.Predef 对象中。 Scala.Predef 自动引入到当前作用域,在这个对象中,同时定义了一个从类型 Any 到 ArrowAssoc 的隐含转换。因此当使用 1 -> “One”时,编译器自动插入从 1 转换到 ArrowAsso c转换。
类型类
类型类是一种非常灵活的设计模式,可以把类型的定义和行为进行分离,让扩展类行为变得非常方便。或者说如果我们想创建跨越类型的功能(即功能实现独立于类型去演变),那么这样的“功能”不是适合也不应该在主类型的层次结构上进行演变的,这时是使用类型类的绝佳场所。因为类型类是一个比较独立的语法,虽然它的实现需要使用到类型类,但是在本文中为了不止于失去焦点,我们不打算在这里详细介绍,而在接下来的一篇文章中进行专门的介绍。
隐式解析的搜索范围
这一部分的规则有些复杂,根据《Scala In Depth》所描述的,顶层的搜索逻辑是:
在当前作用域下查找。这种情形又分两种情况,一个是在当前作用域显示声明的implicit元素,另一个通过import导入的implicit元素。
如果第一种方式没有找到,则编译器会继续在隐式参数类型的隐式作用域里查找。
真正复杂的地方是什么叫一个类型的隐式作用域?一个类型的隐式作用域指的是“与该类型相关联的类型”的所有的伴生对象。
OK,那什么叫作“与一个类型相关联的类型”? 定义如下:
假如T是这样定义的:T with A with B with C,那么A, B, C的伴生对象都是T的搜索区域。
如果T是类型参数,那么参数类型和基础类型都是T的搜索部分。比如对于类型List[Foo],List和Foo都是搜索区域
如果T是一个单例类型p.T,那么p和T都是搜索区域
如果T是类型注入p#T,那么p和T都是搜索区域。
隐式参数
为什么把隐式参数单独拿出来放到最后讲是因为从用意上讲,隐式参数与我们前面讲述的隐式类型转化有很大的差异,虽然它涉及到了关键字implict,但是它做的是另外一件事情。隐含参数有点类似缺省参数,如果在调用方法时没有提供某个参数,编译器会在当前作用域查找是否有符合条件的 implicit 对象可以作为参数传入,不同于缺省参数,隐式参数的值可以在方法调用的前的上下文中指定,这是隐式参数更加灵活的地方。
object ImplicitParamDemo {
object Greeter{
def greet(name:String)(implicit prompt: String) {
println("Welcome, " + name + ". The System is ready.")
println(prompt)
}
}
def main(args: Array[String]) {
implicit val prompt = ">"
Greeter.greet("admin")
}
}