隐式转换和隐式参数是Scala中两个非常强大的功能,利用隐式转换和隐式参数,你可以提供优雅的类库,对类库的使用者隐匿掉那些枯燥乏味的细节。
隐式的对类的方法进行增强,丰富现有类库的功能。
object ImplicitDemo extends App{ //定义隐式类,可以把File转换成定义的隐式类RichFile implicit class RichFile(from:File){ def read:String = Source.fromFile(from.getPath).mkString } //使用隐式类做已有类的动能的扩展 val contents = new File("src/test1.txt").read println(contents) }Scala会自己主动为每一个程序加上几个隐式引用,就像Java程序会自己主动加上java.lang包一样。
Scala中。下面三个包的内容会隐式引用到每一个程序上。所不同的是。Scala还会隐式加进对Predef的引用。
import java.lang._ // in JVM projects, or system namespace in .NET import scala._ // everything in the scala package import Predef._ // everything in the Predef object上面三个包,包括了经常使用的类型和方法。java.lang包包括了经常使用的java语言类型,假设在.NET环境中,则会引用system命名空间。相似的,scala还会隐式引用scala包,也就是引入经常使用的scala类型。
请注意 上述三个语句的顺序藏着一点玄机。
我们知道,通常,假设import进来两个包都有某个类型的定义的话,比方说,同一段程序。即引用了’scala.collection.mutable.Set’又引用了’import scala.collection.immutable.Set’则编译器会提示无法确定用哪一个Set。
这里的隐式引用则不同,假设有同样的类型。后面的包的类型会将前一个隐藏掉。
比方。java.lang和scala两个包里都有StringBuilder。这样的情况下,会使用scala包里定义的那个。java.lang里的定义就被隐藏掉了,除非显示的使用java.lang.StringBuilder。
创建隐式类时,只需要在对应的类前加上implicit关键字。比如:
object Helpers { implicit class IntWithTimes(x: Int) { def times[A](f: => A): Unit = { def loop(current: Int): Unit = if(current > 0) { f loop(current - 1) } loop(x) } } }这个例子创建了一个名为IntWithTimes的隐式类。这个类包含一个int值和一个名为times的方法。要使用这个类,只需将其导入作用域内并调用times方法。比如:
scala> import Helpers._ import Helpers._ scala> 5 times println("HI") HI HI HI HI HI使用隐式类时,类名必须在当前作用域内可见且无歧义,这一要求与隐式值等其他隐式类型转换方式类似。
只能在别的trait/类/对象内部定义。
object Helpers { implicit class RichInt(x: Int) // 正确! } implicit class RichDouble(x: Double) // 错误!构造函数只能携带一个非隐式参数。
implicit class RichDate(date: java.util.Date) // 正确! implicit class Indexer[T](collecton: Seq[T], index: Int) // 错误! implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正确!虽然我们可以创建带有多个非隐式参数的隐式类,但这些类无法用于隐式转换。
在同一作用域内,不能有任何方法、成员或对象与隐式类同名。
object Bar implicit class Bar(x: Int) // 错误! val x = 5 implicit class x(y: Int) // 错误! implicit case class Baz(x: Int) // 错误!是指那种以implicit关键字声明的带有单个参数的函数,这种函数将被自动引用,将值从一种类型转换成另一种类型。
使用隐含转换将变量转换成预期的类型是编译器最先使用 implicit 的地方。这个规则非常简单,当编译器看到类型X而却需要类型Y,它就在当前作用域查找是否定义了从类型X到类型Y的隐式定义。
比如,通常情况下,双精度实数不能直接当整数使用,因为会损失精度:
scala> val i:Int = 3.5 <console>:7: error: type mismatch; found : Double(3.5) required: Int val i:Int = 3.5当然你可以直接调用 3.5.toInt。
这里我们定义一个从 Double 到 Int 的隐含类型转换的定义,然后再把 3.5 赋值给整数,就不会报错。
scala> implicit def doubleToInt(x:Double) = x toInt doubleToInt: (x: Double)Int scala> val i:Int = 3.5 i: Int = 3此时编译器看到一个浮点数 3.5,而当前赋值语句需要一个整数,此时按照一般情况,编译器会报错,但在报错之前,编译器会搜寻是否定义了从 Double 到 Int 的隐含类型转换,本例,它找到了一个 doubleToInt。 因此编译器将把
val i:Int = 3.5 转换成 val i:Int = doubleToInt(3.5)这就是一个隐含转换的例子,但是从浮点数自动转换成整数并不是一个好的例子,因为会损失精度。 Scala 在需要时会自动把整数转换成双精度实数,这是因为在 Scala.Predef 对象中定义了一个
implicit def int2double(x:Int) :Double = x.toDouble而 Scala.Predef 是自动引入到当前作用域的,因此编译器在需要时会自动把整数转换成 Double 类型。
隐式参数
看最开始的例子:
def compare[T](x:T,y:T)(implicit ordered: Ordering[T]):Int = { ordered.compare(x,y) }在函数定义的时候,支持在最后一组参数使用 implicit,表明这是一组隐式参数。在调用该函数的时候,可以不用传递隐式参数,而编译器会自动寻找一个implict标记过的合适的值作为该参数。
例如上面的函数,调用compare时不需要显式提供ordered,而只需要直接compare(1,2)这样使用即可。
再举一个例子:
object Test{ trait Adder[T] { def add(x:T,y:T):T } implicit val a = new Adder[Int] { override def add(x: Int, y: Int): Int = x+y } def addTest(x:Int,y:Int)(implicit adder: Adder[Int]) = { adder.add(x,y) } addTest(1,2) // 正确, = 3 addTest(1,2)(a) // 正确, = 3 addTest(1,2)(new Adder[Int] { override def add(x: Int, y: Int): Int = x-y }) // 同样正确, = -1 }Adder是一个trait,它定义了add抽象方法要求子类必须实现。
addTest函数拥有一个Adder[Int]类型的隐式参数。
在当前作用域里存在一个Adder[Int]类型的隐式值implicit val a。
在调用addTest时,编译器可以找到implicit标记过的a,所以我们不必传递隐式参数而是直接调用addTest(1,2)。而如果你想要传递隐式参数的话,你也可以自定义一个传给它,像后两个调用所做的一样。