1. 程式人生 > >scala的隱式轉換

scala的隱式轉換

 隱式轉換的本質:可以減少從一個型別顯式轉換成另一個型別的需要。

1.隱式規則

 隱式定義:指允許編譯器插入程式以解決型別錯誤的定義。隱式轉換受一下規則約束:

1)標記規則:只有標記為implicit的定義才可用。

2)作用域規則:被插入的隱式轉換必須是當前作用域的單個識別符號,或者跟隱式轉換的源型別或目標型別有關聯。也就是說編譯器會從隱式轉換的源型別或目標型別的伴生物件中查詢隱式定義。

3)每次一個原則:每次只能有一個隱式定義被插入。

4)顯式優先原則:只要程式碼按編寫的樣子能通過型別檢查,就不嘗試隱式定義。

2.隱式定義的使用

 Scala總共會有3個地方會使用到隱式定義:轉換到一個預期的型別;對成員轉換接收端(欄位、方法);隱式引數。

2.1隱式轉換到一個預期的型別

 當方法的引數型別是X的時候,如果想要把型別Y的引數傳進去,可以使用使用隱式定義。

class Food(val name:String) {}
object Food {
  implicit def string2Food1(s:String) = new Food(s)
}
class Cat {
  implicit def string2Food2(s:String) = new Food(s)
  def eat(food:Food) = println(s"cat eat ${food.name}")
}

import
Yin.string2Food3 object test { implicit def string2Food4(s:String) = new Food(s) def main(args:Array[String]) = { val cat = new Cat cat.eat("fish") } implicit def string2Food5(s:String) = new Food(s) } object Yin { implicit def string2Food3(s:String) = new Food(s) }

 Cat類的eat方法需要的是Food型別的引數,現在把String型別的引數新增進去,所以需要加隱式定義。這裡的隱式轉換是把String型別轉換為Food型別。

1)string2Food1定義在Food的伴生物件中,因而是可以起作用的

2)string2Food2定義在Cat類中,不在當前作用域也不在源型別或目標型別中,因此不起作用

3)string2Food3是把隱式定義放在一個專門的物件中,之後再引用,因此是可以的

4)string2Food4在當前作用域中,起作用

5)string2Food5不在當前作用域中,沒有用處

2.2轉換接收端

 隱式轉換可以應用於方法呼叫的接收端,比如有個obj.doit方法,假設obj沒有doit這個方法,那麼可以把obj隱式轉換為另一個有doit方法的物件,假裝obj有doit這個方法。下面是給Int型別假裝有**這個方法:

//下面空行代表類在不同檔案中
import Invisib.int2Calcu
object test{
  def main(args:Array[String]) = {
    val a = 1 ** 2
    println(a)
  }
}

class Calcu(val x:Int) {
  def **(y:Int) : Int = x * (y + 1) - 3
}

object Invisib {
  implicit def int2Calcu(x:Int) = new Calcu(x)
}

 隱式類

 隱式類以implicit修飾,編譯器會生成一個從類的構造方法引數到類本身的隱式轉換。比如下面的StringMaker會自動生成implicit def StringMaker(str:String) = new StringMaker(str)。

//下面的空行代表類在不同檔案中
import StringUtils.StringMaker
object test {
  def main(args:Array[String]) = {
    println("abcdefg" - 2)
  }
}

object StringUtils {
  implicit class StringMaker(str:String) {
    def -(len:Int) = {
      val length = str.length
      if(len <= length)
        str.substring(0, length - len)
      else
        ""
    }
  }
}

 隱式類有以下的約束:

1)隱式類不能是樣例類

2)其構造方法必須有且僅有一個

3)隱式類必須在另一個物件、類或特質裡面

2.3隱式引數

 方法的最後一個引數列表可以被隱式的提供,在呼叫時可以不需要最後一個引數列表的引數。

def test(implicit x:Int, y:Int) = {}
//下面這個方法提供的不是引數列表,編譯錯誤
//def test(x:Int, implicit y:Int) = {}

 下面給出一個簡單的例子用於理解隱式引數:

//下面空行代表類在不同的檔案
import Tech.hobby object test { def main(args:Array[String]) = { val t = new Tech println(t) } } class Tech { def printHobby(implicit hobby:String) = println(hobby) } object Tech { implicit val hobby = "Listen" }

 3.上下文界定

 上下文界定是當需要一個M[T]型別的隱式值時,其中T是泛型,如果方法體內可以去掉對隱式值的使用,則可以省掉這個引數的名稱使用上下文界定T:M縮短方法簽名。

object test {
  def main(args:Array[String]) = {
    val bg = bigger(1, 5)
  }

  def bigger[T](x:T, y:T)(implicit ordering:Ordering[T]) : T = {
    if(ordering.gt(x, y)) x
    //去掉方法體中對隱式值ordering的引用
    //if(implicitly[Ordering[T]].gt(x, y)) x
      else y
  }
}

 bigger方法是用來對比任意型別的2個值的,因為型別的不同,使用Ordering[T]型別的隱式值用來對比,在方法體中可以使用標準類庫的方法:def implicitly[T](implicit t:T) = t 去掉對ordering的引用。因為在標準類庫中很多常見的型別都提供了這種排序方法,所以隱式值ordering不需要自己提供,下面使用上下文界定縮短方法:

object test {
  def main(args:Array[String]) = {
    val bg = bigger(1, 5)
  }

  //上下文界定
  def bigger[T:Ordering](x:T, y:T) : T = {
      if(implicitly[Ordering[T]].gt(x, y)) x
      else y
  }
}

 4.上界、下界與檢視界定

4.1上界、下界

 在使用泛型的時候,可以指定泛型型別是某個類的子類,或是某個類的父類,從而約束泛型的類型範圍。T <: M代表上界,T需要是型別M的子類或本身;T >: M代表下界,T需要是M的父類或本身。在下面的例子中,為了能進行compareTo方法的對比,引數型別需要是實現Comparable[T]的。

object test {
  def main(args:Array[String]) = {
    val cc = bigger("abc", "cba")
  }

  def bigger[T <: Comparable[T]](x:T, y:T) : T = {
    if (x.compareTo(y) > 0) x 
      else y
  }
}

4.2檢視界定

 在上面的那個例子中,String型別因為實現了Comparable[T],所以可以呼叫bigger方法,而如果使用bigger(1, 2),則會報錯。這時可以使用檢視界定<%,編譯器會查詢Int型別的隱式轉換型別有沒有是實現Comparable的,下面是檢視界定的例子:

object test {
  def main(args:Array[String]) = {
    val cc = bigger(1, 2)
  }

  def bigger[T <% Comparable[T]](x:T, y:T) : T = {
    if (x.compareTo(y) > 0) x 
      else y
  }
}