【scala 筆記(9)】 隱式轉換 和 隱式參數-- implicit

隱式轉換和隱式參數是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.scalaWrapAsScalaWrapAsJava 特質定義了大量隱式轉換用於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 ...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章