隐式转换和隐式参数是Scala的两个功能强大的工具, 在幕后处理那些很有价值的工作。
隐式转换
所谓隐式转换函数
(implicit conversion function)指的是那种以implicit关键字声明的带有 单个参数 的函数。 正如它的名称所表达的, 这样的函数将被 自动应用 ,将值从 一种类型转换为另一种类型。
class Fraction(private var x:Int, private var y:Int){
def * (other:Fraction) :Fraction = {
new Fraction(this.x*other.x, this.y * other.y)
}
override def toString: String = "%s/%s".format(this.x, this.y)
}
object Fraction{
// 隐式转换函数定义, 方法名通常命名为 source2Target
implicit def int2Fraction(v :Int) :Fraction = new Fraction(v,1)
}
object Test {
def main(args: Array[String]): Unit = {
val f1 = new Fraction(1,3)
val f2 = new Fraction(2,7)
// 正常调用 *
println((f1 * f2).toString)
// 通过隐式转换进行调用 int2Fraction(2) * f2
println((2 * f2).toString)
}
}
隐式函数将整数2转为一个Fraction对象,这个对象接着又被乘以f2.
你可以对转换函数任意命名,但不过建议采用 source2Target 这种约定的命名方式。
利用隐式转换丰富现有类库功能
你是否希望某个类中有某个方法, 而这个类的作者却没有提供, 例如 java.io.File类能有个read 方法来读取文件:
// 可惜在java 中不存在该方法
val content = new File("README.md").read
在scala中 可以通过隐式转换进行类库功能方法添加:
class RichFile(val from: File){
def read = Source.fromFile(from)
}
然后再提供一个隐式转换来将File变为RichFile:
implicit def file2RichFile(f:File) = new RichFile(f)
这样一来, 你就可以在File 对象上进行read方法调用。
引入隐式转换
Scala会考虑如下的隐式转换函数:
1. 位于源或目标类型的伴生对象中的隐式函数;
2. 位于当前作用域可以以单个标识符指代的隐式函数;
比如上述定义的int2Fraction函数,定义在了Fraction伴生对象中,这样它就能够用来将整数转换为分数。
假如Fraction 类和对象定义在 com.borey.scalautil包中。如果你想要使用必须像这样:
import com.borey.scalautil.Fraction._
隐式转换规则:
隐式转换在如下三种各不相同的情况会被考虑:
- 当表达式的类型与预期的类型不同时;
- 当对象访问一个不存在的成员时;
- 当对象调用某个方法,而该方法的参数申明与传入参数不匹配时;
另一方面,也有三种情况编译器不会尝试使用隐式转换:
- 如果代码能够在不使用隐式转换的前提下通过编译,则不会使用隐式转换;
- 编译器不会尝试同时执行多个转换;
- 存在二义性的转换时个错误的; 例如 int2A(a) * b 和 int2B(a) * b 都是合法的, 编译器将会报错。
隐式参数
函数或方法可以带有一个标记为implicit的参数列表。这种情况下,编译器将会查找缺省值, 提供给该函数或方法。以下是一个简单的实例:
case class Delimiters(left:String, right:String)
def show(msg: String)(implicit delims:Delimiters) = {
println(delims.left + msg + delims.right)
}
上述定义了一个柯里化函数 show 和 一个样例类Delimiters:
- 显示调用
scala> show("hello borey")(Delimiters("{", "}"))
{hello borey}
- 隐式调用
scala> implicit val show_delims = Delimiters("<", ">")
show_delims: Delimiters = Delimiters(<,>)
scala> show("hello borey")
<hello borey>
在这种情况下, 编译器将会查找一个类型为 Delimiters的隐式值。 这必须是一个被申明为implicit的值。 编译器将会在如下两个地方查找这样的一个对象:
1. 在当前作用域所有可以用单个标识符指代的满足类型要求的val 和 def。
2. 与所要求类型相关联的类型的伴生对象。相关联的类型包括要求类型本身以及它的类型参数(如果它是一个参数化的类型的话)
如果定义了两个类型为 Delimiters的隐式值 呢 ? 测试一下:
scala> implicit val show_delims = Delimiters("<", ">")
show_delims: Delimiters = Delimiters(<,>)
scala> implicit val show_delims2 = Delimiters("<<", ">>")
show_delims2: Delimiters = Delimiters(<<,>>)
scala> show("hello borey")
<console>:21: error: ambiguous implicit values:
both value show_delims of type => Delimiters
and value show_delims2 of type => Delimiters
match expected type Delimiters
show("hello borey")
我们可以看到,编译器会产生Error: 模糊不清的隐式值; 所以在使用的时候要谨慎些。
Scala程序隐式引入
每个Scala程序都隐式的以如下代码开始:
import java.lang._
import scala._
import Predef._
其实Predef对象中定义了大量常用的隐式转换, 感兴趣的可以去看下 Predef.scala源码。
在REPL中, 键入 :implicits
查看所有除Predef外被引入的隐式成员, 或者键入 :implicits -v
查看全部:
scala> :implicit
/* 1 implicit members imported from Fraction */
/* 1 defined in Fraction */
implicit def int2Fraction(v: Int): Fraction
scala> :implicit -v
/* 69 implicit members imported from scala.Predef */
/* 7 inherited from scala */
final implicit class ArrayCharSequence extends CharSequence
final implicit class ArrowAssoc[A] extends AnyVal
final implicit class Ensuring[A] extends AnyVal
......
/* 40 inherited from scala.Predef */
implicit def ArrowAssoc[A](self: A): ArrowAssoc[A]
implicit def Ensuring[A](self: A): Ensuring[A]
implicit def StringFormat[A](self: A): StringFormat[A]
......
/* 22 inherited from scala.LowPriorityImplicits */
implicit def genericWrapArray[T](xs: Array[T]): mutable.WrappedArray[T]
implicit def wrapBooleanArray(xs: Array[Boolean]): mutable.WrappedArray[Boolean]
implicit def wrapByteArray(xs: Array[Byte]): mutable.WrappedArray[Byte]
......
/* 1 implicit members imported from Fraction */
/* 1 defined in Fraction */
implicit def int2Fraction(v: Int): Fraction
Scala与Java集合的互操作
有时候, 你可能需要使用Java集合, 你多半会怀念Scala集合上那些丰富的处理方法。反过来讲, 你可能会想要构建出一个Scala集合, 然后传递给Java代码。
Scala 2.12.0 之前版本
JavaConversions 对象提供了用于在Scala和Java集合之间来回转换的一组方法。例如:
scala> import scala.collection.JavaConversions.mapAsScalaMap
import scala.collection.JavaConversions.mapAsScalaMap
scala> val b: scala.collection.mutable.Map[String,String] = System.getenv
<console>:15: warning: object JavaConversions in package collection is deprecated (since 2.12.0): use JavaConverters
val b: scala.collection.mutable.Map[String,String] = System.getenv
^
b: scala.collection.mutable.Map[String,String] = Map(PATH -> /home/prod/softwares/scala/scala-2.12.2/bin: ...
可以查看 JavaConversions.scala 中 WrapAsScala 和 WrapAsJava 特质定义了大量隐式转换用于Java和Scala集合之间。
/* __ *\
** ________ ___ / / ___ Scala API **
** / __/ __// _ | / / / _ | (c) 2006-2016, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ | http://www.scala-lang.org/ **
** /____/\___/_/ |_/____/_/ | | **
** |/ **
\* */
package scala
package collection
import convert._
/** A variety of implicit conversions supporting interoperability between
* Scala and Java collections.
*
* The following conversions are supported:
*{{{
* scala.collection.Iterable <=> java.lang.Iterable
* scala.collection.Iterable <=> java.util.Collection
* scala.collection.Iterator <=> java.util.{ Iterator, Enumeration }
* scala.collection.mutable.Buffer <=> java.util.List
* scala.collection.mutable.Set <=> java.util.Set
* scala.collection.mutable.Map <=> java.util.{ Map, Dictionary }
* scala.collection.concurrent.Map <=> java.util.concurrent.ConcurrentMap
*}}}
* In all cases, converting from a source type to a target type and back
* again will return the original source object:
*
*{{{
* import scala.collection.JavaConversions._
*
* val sl = new scala.collection.mutable.ListBuffer[Int]
* val jl : java.util.List[Int] = sl
* val sl2 : scala.collection.mutable.Buffer[Int] = jl
* assert(sl eq sl2)
*}}}
* In addition, the following one way conversions are provided:
*
*{{{
* scala.collection.Seq => java.util.List
* scala.collection.mutable.Seq => java.util.List
* scala.collection.Set => java.util.Set
* scala.collection.Map => java.util.Map
* java.util.Properties => scala.collection.mutable.Map[String, String]
*}}}
*
* The transparent conversions provided here are considered
* fragile because they can result in unexpected behavior and performance.
*
* Therefore, this API has been deprecated and `JavaConverters` should be
* used instead. `JavaConverters` provides the same conversions, but through
* extension methods.
*
* @author Miles Sabin
* @author Martin Odersky
* @since 2.8
*/
@deprecated("use JavaConverters", since="2.12.0")
object JavaConversions extends WrapAsScala with WrapAsJava
Scala 2.12.0 之后版本
JavaConversions在2.12.0向后开始被废弃, 因为可能会导致不可预测的意外行为发生,建议采用JavaConverters 对象进行显式方法调用, 例如:
scala> import scala.collection.JavaConverters.mapAsScalaMap
import scala.collection.JavaConverters.mapAsScalaMap
scala> val b: scala.collection.mutable.Map[String,String] = mapAsScalaMap(System.getenv)
b: scala.collection.mutable.Map[String,String] = Map(PATH -> /h ...