隱式轉換和隱式參數是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 ...