scala隱士轉換理解

Scala提供的隱式轉換特性能夠在效果上給一個類增長一些方法,或者用於接收不一樣類型的對象.

        然而使用Scala的隱式轉換是有必定的限制的,總結以下:

implicit關鍵字只能用來修飾方法、變量(參數)和伴隨對象。
隱式轉換的方法(變量和伴隨對象)在當前範圍內纔有效。若是隱式轉換不在當前範圍內定義(好比定義在另外一個類中或包含在某個對象中),那麼必須經過import語句將其導。舉例

package demo {
package util {
 
import java.util.Date
import java.text.SimpleDateFormat
 
object DateUtil {
  class DateWrapper(date: Date) {
    def format(str: String) = new SimpleDateFormat(str).format(date)
  }
  implicit def toDateWrapper(date: Date) = new DateWrapper(date)
}
}
 
package service {
import java.text.SimpleDateFormat
import java.util.Date
   // 注: 必須將object Rest的定義放在class Rest以前,或者顯式指出str2Date的返回類型,不然編譯不經過
   object Rest{
    class RichDate(str: String){
      def toDate(): Date = new SimpleDateFormat("yyyy-MM-dd").parse(str)
    }
    implicit def str2Date(str: String) = new RichDate(str)
  }
  class Rest {
     import util.DateUtil._
     import Rest._
      // 必須把demo.util包下的伴隨對象DateWrapper中的全部成員引進來
     def today = new Date().format("yyyy-MM-dd");
     // 伴隨對象 Rest中定義了一個隱式轉換函數
     def getDate(str: String): Date = str.toDate();
  }
 
}
 
object SC4 {
  import demo.service.Rest
   def main(args: Array[String]) ={
     println (new Rest().today)
     println(new Rest().getDate("2011-01-09"))
   }
}
}
但有一種狀況例外,源類型或目標類型(包括源類型和目標類型的類型變量的類型)的伴隨對象中隱式轉換函數(或變量,伴隨對象)不須要顯示導入。 好比

package demo{
object MyTestApp{
  def main(args: Array[String]): Unit = {
    val myTest = new MyTest();
    myTest.printInt(4)
    myTest.printYourTest(myTest)  // the source type is of MyTest while the target require YourTest
    myTest.fuxkTest()  //  there is no method on MyTest but we can still call it 
  }
}
 
class YourTest{
  override def toString = "Your Test"
   def  fuxkTest() = print("fuxk Test");
}
 
 object YourTest{
    implicit def myTest2YourTest = new YourTest
 }
 
class MyTest{
  import MyTest._
  def printStr(str: String) = println(str)
 
   // you can't do it like `printStr(i)` unless you bring the implicit converter `MyTest.int2String`into scope
  def printInt(i: Int) = printStr(i)
 
  def printYourTest(obj: YourTest) = println(obj)
 
  def getYorTest(): YourTest = this;
}
   
object  MyTest {
  implicit def int2String(i: Int ): String = i.toString
  implicit def myTest2YourTest(obj: MyTest): YourTest= new YourTest
//  implicit val myTest2YourTest = (obj : MyTest) => new YourTest
}
}  
這樣規定的好處是,經過限制隱式轉換的有效範圍,使維護和理解代碼變得相對容易.
通常來講,scala編譯器會首先在方法調用處的當前範圍內查找隱式轉換函數;若是沒有找到,會嘗試在源類型或目標類型(包括源類型和目標類型的類型變量的類型)的伴隨對象中查找轉換函數,若是仍是沒找到,則拒絕編譯。 

好比: 

object ABCDMain extends App {
 
  class B
 
  class C {
     override def toString() = "I am C";
     def printC(c: C) = println(c);
  }
 
  class D
 
  implicit def B2C(b: B) = {
    println("B2C")
    new C
  }
 
  implicit def D2C(d: D) = {
    println("D2C")
    new C
  }
   
  new D().printC(new B) 
}
運行上述代碼,先調用 D2C轉換函數將new D()轉換成C類, 而後調用C類的printC方法;但發現傳入的參數類型是B類,因而搜索當前範圍有無合適的轉換函數,發現B2C轉換函數符合要求。

又好比:

object ABCDMain extends App {
  class A
  
  object A {
  //  implicit def A2C(a: A) = {
  //    println("A.A2C");
  //    new C
  //  }
  }
  
  class C {
    override def toString() = "I am C";
    def printC(c: C) = println(c);
  }
   
  object C {
    implicit def A2C(a: A) = {
      println("C.A2C");
      new C
    }
  }
 
  class D
 
  implicit def D2C(d: D) = {
    println("D2C")
    new C
  }
   
  new D().printC(new A)
}
運行上述代碼,先調用 D2C轉換函數將new D()轉換成C類, 而後調用C類的printC方法, 但發現傳入的參數類型是A類。因爲當前範圍無合適的轉換函數,故搜索object A和object C內有無合適的轉換函數,最後發現object A內有合適的轉換函數。
若是同時在object A和object C內發現合適的轉換函數,有可能致使編譯錯誤。
    再好比:
object ABCDMain extends App {
 
  class A
   
  object A{
  //implicit def MA2MC(ma: M[A]) ={
  //    new M[C];
  //}
  }
 
  class C
 
  object C{
    implicit def MA2MC(ma: AnyRef) = {
      new M[C];
    }
  }
 
  class D {
     def printM(m: M[C]) = println("i am M[C]");
  } 
   
  class M[T]
 
  object M {
    implicit def MA2MC(ma: M[A]) = {
      new M[C];
    }
  }
  
  new D().printM(new M[A]) 
}
運行上述代碼,printM須要傳入類型爲M[C]的參數,因爲傳入了類型爲M[A],又在當前範圍內沒有合適轉換函數, 所以同時在object M,object A和object C內搜索合適的轉換函數,若是發現兩個或以上合適的轉換函數,那麼有可能致使編譯錯誤。

源類型和目標類型最多隻發生一次隱式轉換。舉例

class A{
override def toString() = "I am A";
}
class B{
override def toString() = "I am B";
}
class C{
override def toString() = "I am C";
}
implicit def A2B(a: A) = new B
implicit def B2C(b: B) = new C
 
def printC(c: C) = println(c);
printC(new B); // 會調用B2C函數進行隱式轉換
printC(new A); // 對 new A 不能連續作兩次隱式轉換
執行printC(new A)時會報類型不匹配的錯
注意:這裏所說的」最多隻發生一次隱式轉換「的意思是,源類型最多隻通過一次函數轉換變成目標類型(或與目標類型兼容的類型),但在一次方法調用中,可能發生屢次源類型和目標類型之間的轉換。
好比

class C {
  override def toString() = "I am C";
  def printC(c: C) = println(c);
}
 
class D 
 
implicit def B2C (b: B) = {
   println("B2C");
   new C
}
implicit def D2C(d: D) = {
   println("D2C");
   new C
}
 
new D().printC(new B); // 這裏會發生兩次隱式轉換, 一次是D2C, 一次是B2C
另外,當函數定義了隱式參數時,也可能發生屢次隱式轉換:

object Main extends App {
  class PrintOps() {
    def print(implicit i: Int) = println(i);
  }
 
  implicit def user2PrintOps(s: String) = {
    println("use2PrintOps")
    new PrintOps
  }
 
  implicit def str2int(implicit s: String, implicit l: List[Int]): Int = {
    println("str2int")
    Integer.parseInt(s)
  }
 
  implicit def getString = {
    println("getString")
    "123"
  }
 
  implicit def newList = {
    println("newList")
    List(2)
  }
 
  "a".print
}
運行上述代碼,首先調用user2PrintOps函數將"a"轉換成PrintOps, 而後調用print方法。因爲調用print時沒有顯式提供implicit參數,所以嘗試在當前範圍內搜索合適的implicit轉換值。編譯器發現str2int這個隱式轉換函數能提供print所需int類型,但str2int又須要一個隱式的String和一個隱式的List[Int]。編譯器繼續在當前範圍內搜索,最後發現getString和newList這兩個隱式轉換函數能分別提供一個String實例和一個List[Int]實例。至此編譯器知道要先調用getString和newList,再調用str2int,最後調用print。 

若是當前的類型匹配或類型兼容,則不會進行隱式轉換。舉例:

class AA extends A{
override def toString() = "I am AA which inherits from A"
}
implicit def AA2A(aa: AA) = {
  println("AA --> A");
  new A
}
 
def printA(a: A) = println(a);
printA(new AA)  // 由於AA是A的子類型,因此能夠把AA類型的值傳遞給A類型的變量
 若是當前範圍內有兩個或以上合適的隱式轉換函數,Scala會怎麼處理呢?
在Scala 2.7以及以前的版本中,編譯器會發出錯誤。這跟重載的狀況是同樣的。好比有兩個重載方法foo,一個接收String參數,另外一個接收AnyRef參數,當foo(null) 這樣寫的時候,編譯器會拒絕編譯。但在Scala 2.8中,編譯器會選擇foo(String)這個重載方法,即編譯器會選擇一個更具體的方法。一樣當碰到兩個或兩個以上合適的隱式轉換函數時,編譯器也會選擇一個更具體的方法。至於哪一個方法被認爲更具體,能夠根據如下的規則進行判斷:
    a) 前者的函數參數類型是後者的子類型,則前者更具體 
  b)  假設兩個隱式轉換函數都定義在一個類中,若是前者所在的類是後者的子類,那麼前者更具體


        因爲隱式轉換會給代碼帶來「魔幻」效果,對於不熟悉這種特性的人會感受難受。 筆者曾經發現一個利用隱式轉換改變方法執行順序的例子: 

// TernaryOp對象提供了相似java的三目運算符號操做
object TernaryOp {
  class Ternary[T](t: T) {
    println("Ternary") 
    def is[R](bte: BranchThenElse[T,R]) = {
       println("is ... ")
       if (bte.branch(t)) bte.then(t) else bte.elze(t)
   } 
 }
  class Branch[T](branch: T => Boolean) {
    println("branch");
    def ?[R] (then: T => R) = new BranchThen(branch,then)
  }
   
  class BranchThen[T,R](val branch: T => Boolean, val then: T => R){
     println("BranchThen")
  }
   
  class Elze[T,R](elze: T => R) {
     println("Elze")
    def :: (bt: BranchThen[T,R]) = new BranchThenElse(bt.branch,bt.then,elze)
  }
   
  class BranchThenElse[T,R](val branch: T => Boolean, val then: T => R, val elze: T => R)
   
  implicit def any2Ternary[T](t: T) = new Ternary(t)
  implicit def fct2Branch[T](branch: T => Boolean) = new Branch(branch)
  implicit def fct2Elze[T,R](elze: T => R) = new Elze(elze)
   
  def test = {
  this.getClass.getSimpleName is {s: String => s.endsWith("$")} ? {s: String => s.init} :: {s: String => s}
}
}
 
TernaryOp.test // 思考一下test中會發生怎樣轉換?若搞不明白,請參考<a href="https://gist.github.com/1388106" target="_blank" rel="nofollow">https://gist.github.com/1388106</a>
        筆者以爲,如不必儘可能少用隱式轉換,畢竟太「魔幻」的代碼理解起來仍是要費點勁。  

BTW:

命名函數的參數能夠聲明爲implicit,但implicit必須出如今首位,而且是對全部的參數有效,不能只給某些參數聲明爲implicit,好比:
def maxFunc(implicit i1: Int, i2: Int) = i1 + i2
maxFunc帶有兩個implicit參數i1和i2。你沒法只聲明一個implicit參數。你不能這樣寫 
def maxFunc( i1: Int, implicit i2: Int) = i1 + i2
也不能這樣寫: 
def maxFunc(implicit i1: Int, implicit i2: Int) = i1 + i2
若是你只想聲明一個implicit,使用curry,如
def maxFunc(implicit i1: Int)(i2: Int) = i1 + i2
    2. 匿名函數不能聲明隱式參數,即不能這樣寫:

val f = (implicit s: String) => s+1

   3. 若是一個函數帶有implicit參數,則沒法經過 _ 獲得該函數引用。你嘗試這樣作是沒法編譯的:

def maxFunc(implicit i1: Int, i2: Int) = i1 + i2
val f = maxFunc _     // 編譯錯誤
   4. 能夠給匿名函數的參數加上implicit,好比:
def h( implicit s: String) = println("here : "+s)
def g(func: String => Int) = {
  println(func("a"))  
}
g{
 implicit s =>  h; 2
}
這裏的implicit s => h; 2至關於 s => implicit val xx = s; h; 2. 
若是匿名函數有兩個參數,貌似不能給參數加上implicit
相關文章
相關標籤/搜索