隐式转换指的是以implicit
关键字声明带有单个参数的转换函数,它将值从一种类型转换为另外一种类型,以便使用以前类型所没有的功能。示例以下:java
// 普通人 class Person(val name: String) // 雷神 class Thor(val name: String) { // 正常状况下只有雷神才能举起雷神之锤 def hammer(): Unit = { println(name + "举起雷神之锤") } } object Thor extends App { // 定义隐式转换方法 将普通人转换为雷神 一般建议方法名使用source2Target,即:被转换对象To转换对象 implicit def person2Thor(p: Person): Thor = new Thor(p.name) // 这样普通人也能举起雷神之锤 new Person("普通人").hammer() } 输出: 普通人举起雷神之锤
并非你使用implicit
转换后,隐式转换就必定会发生,好比上面若是不调用hammer()
方法的时候,普通人就仍是普通人。一般程序会在如下状况下尝试执行隐式转换:git
而在如下三种状况下编译器不会尝试执行隐式转换:程序员
convert1(convert2(a))*b
;这里首先解释一下二义性,上面的代码进行以下修改,因为两个隐式转换都是生效的,因此就存在了二义性:github
//两个隐式转换都是有效的 implicit def person2Thor(p: Person): Thor = new Thor(p.name) implicit def person2Thor2(p: Person): Thor = new Thor(p.name) // 此时下面这段语句没法经过编译 new Person("普通人").hammer()
其次再解释一下多个转换的问题:编程
class ClassA { override def toString = "This is Class A" } class ClassB { override def toString = "This is Class B" def printB(b: ClassB): Unit = println(b) } class ClassC class ClassD object ImplicitTest extends App { implicit def A2B(a: ClassA): ClassB = { println("A2B") new ClassB } implicit def C2B(c: ClassC): ClassB = { println("C2B") new ClassB } implicit def D2C(d: ClassD): ClassC = { println("D2C") new ClassC } // 这行代码没法经过编译,由于要调用到printB方法,须要执行两次转换C2B(D2C(ClassD)) new ClassD().printB(new ClassA) /* * 下面的这一行代码虽然也进行了两次隐式转换,可是两次的转换对象并非一个对象,因此它是生效的: * 转换流程以下: * 1. ClassC中并无printB方法,所以隐式转换为ClassB,而后调用printB方法; * 2. 可是printB参数类型为ClassB,然而传入的参数类型是ClassA,因此须要将参数ClassA转换为ClassB,这是第二次; * 即: C2B(ClassC) -> ClassB.printB(ClassA) -> ClassB.printB(A2B(ClassA)) -> ClassB.printB(ClassB) * 转换过程1的对象是ClassC,而转换过程2的转换对象是ClassA,因此虽然是一行代码两次转换,可是仍然是有效转换 */ new ClassC().printB(new ClassA) } // 输出: C2B A2B This is Class B
隐式转换的能够定义在如下三个地方:app
上面咱们使用的方法至关于直接定义在执行代码的做用域中,下面分别给出其余两种定义的代码示例:ide
定义在原类型的伴生对象中:函数
class Person(val name: String) // 在伴生对象中定义隐式转换函数 object Person{ implicit def person2Thor(p: Person): Thor = new Thor(p.name) }
class Thor(val name: String) { def hammer(): Unit = { println(name + "举起雷神之锤") } }
// 使用示例 object ScalaApp extends App { new Person("普通人").hammer() }
定义在一个公共的对象中:大数据
object Convert { implicit def person2Thor(p: Person): Thor = new Thor(p.name) }
// 导入Convert下全部的隐式转换函数 import com.heibaiying.Convert._ object ScalaApp extends App { new Person("普通人").hammer() }
注:Scala自身的隐式转换函数大部分定义在
Predef.scala
中,你能够打开源文件查看,也能够在Scala交互式命令行中采用:implicit -v
查看所有隐式转换函数。this
在定义函数或方法时可使用标记为implicit
的参数,这种状况下,编译器将会查找默认值,提供给函数调用。
// 定义分隔符类 class Delimiters(val left: String, val right: String) object ScalaApp extends App { // 进行格式化输出 def formatted(context: String)(implicit deli: Delimiters): Unit = { println(deli.left + context + deli.right) } // 定义一个隐式默认值 使用左右中括号做为分隔符 implicit val bracket = new Delimiters("(", ")") formatted("this is context") // 输出: (this is context) }
关于隐式参数,有两点须要注意:
1.咱们上面定义formatted
函数的时候使用了柯里化,若是你不使用柯里化表达式,按照一般习惯只有下面两种写法:
// 这种写法没有语法错误,可是没法经过编译 def formatted(implicit context: String, deli: Delimiters): Unit = { println(deli.left + context + deli.right) } // 不存在这种写法,IDEA直接会直接提示语法错误 def formatted( context: String, implicit deli: Delimiters): Unit = { println(deli.left + context + deli.right) }
上面第一种写法编译的时候会出现下面所示error
信息,从中也能够看出implicit
是做用于参数列表中每一个参数的,这显然不是咱们想要到达的效果,因此上面的写法采用了柯里化。
not enough arguments for method formatted: (implicit context: String, implicit deli: com.heibaiying.Delimiters)
2.第二个问题和隐式函数同样,隐式默认值不能存在二义性,不然没法经过编译,示例以下:
implicit val bracket = new Delimiters("(", ")") implicit val brace = new Delimiters("{", "}") formatted("this is context")
上面代码没法经过编译,出现错误提示ambiguous implicit values
,即隐式值存在冲突。
引入隐式参数和引入隐式转换函数方法是同样的,有如下三种方式:
咱们上面示例程序至关于直接定义执行代码的上下文做用域中,下面给出其余两种方式的示例:
定义在隐式参数对应类的伴生对象中;
class Delimiters(val left: String, val right: String) object Delimiters { implicit val bracket = new Delimiters("(", ")") }
// 此时执行代码的上下文中不用定义 object ScalaApp extends App { def formatted(context: String)(implicit deli: Delimiters): Unit = { println(deli.left + context + deli.right) } formatted("this is context") }
统必定义在一个文件中,在使用时候导入:
object Convert { implicit val bracket = new Delimiters("(", ")") }
// 在使用的时候导入 import com.heibaiying.Convert.bracket object ScalaApp extends App { def formatted(context: String)(implicit deli: Delimiters): Unit = { println(deli.left + context + deli.right) } formatted("this is context") // 输出: (this is context) }
def smaller[T] (a: T, b: T) = if (a < b) a else b
在Scala中若是定义了一个如上所示的比较对象大小的泛型方法,你会发现没法经过编译。对于对象之间进行大小比较,Scala和Java同样,都要求被比较的对象须要实现java.lang.Comparable接口。在Scala中,直接继承Java中Comparable接口的是特质Ordered,它在继承compareTo方法的基础上,额外定义了关系符方法,源码以下:
trait Ordered[A] extends Any with java.lang.Comparable[A] { def compare(that: A): Int def < (that: A): Boolean = (this compare that) < 0 def > (that: A): Boolean = (this compare that) > 0 def <= (that: A): Boolean = (this compare that) <= 0 def >= (that: A): Boolean = (this compare that) >= 0 def compareTo(that: A): Int = compare(that) }
因此要想在泛型中解决这个问题,有两种方法:
object Pair extends App { // 视图界定 def smaller[T<% Ordered[T]](a: T, b: T) = if (a < b) a else b println(smaller(1,2)) //输出 1 }
视图限定限制了T能够经过隐式转换Ordered[T]
,即对象必定能够进行大小比较。在上面的代码中smaller(1,2)
中参数1
和2
其实是经过定义在Predef
中的隐式转换方法intWrapper
转换为RichInt
。
// Predef.scala @inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
为何要这么麻烦执行隐式转换,缘由是Scala中的Int类型并不能直接进行比较,由于其没有实现Ordered
特质,真正实现Ordered
特质的是RichInt
。
Scala2.11+后,视图界定被标识为废弃,官方推荐使用类型限定来解决上面的问题,本质上就是使用隐式参数进行隐式转换。
object Pair extends App { // order既是一个隐式参数也是一个隐式转换,即若是a不存在 < 方法,则转换为order(a)<b def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (a < b) a else b println(smaller(1,2)) //输出 1 }
更多大数据系列文章能够参见我的 GitHub 开源项目: 程序员大数据入门指南