天天看點

Scala隐式轉換和隐式參數Scala隐式轉換和隐式參數

Scala隐式轉換和隐式參數

在scala語言中,隐式轉換是一項強大的語言功能,他不僅能夠簡化程式設計,也能夠使程式具有很強的靈活性。要想更進一步地掌握scala語言,了解其隐式轉換的作用和原理是很有必要的,否則很難得以應手的處理日常開發中的問題。

在scala語言中,隐式轉換是無處不在的,隻不過scala語言為我們隐藏了相應的細節,例如scala中的繼承層次結構中:

Scala隐式轉換和隐式參數Scala隐式轉換和隐式參數

它們存在固有的隐式轉換,不需要人工進行幹預,例如Float在必要的情況下自動轉換為Double類型。

1 隐式轉換函數

// 定義了一個隐式轉換函數double2Int,将傳入參數從Double類型轉換為Int類型
implicit def double2Int(x:Double) = x.toInt
val x:Int = 
// x = 3
           

隐式函數的名稱對結構沒有影響,即implicit def double2Int(x:Double)=x.toInt函數可以是任何名字,隻不過采用source2Target這種方式函數的意思比較明确,閱讀代碼的人可以見名知義,增加代碼的可讀性。

隐式轉換功能非常強大,可以快速擴充現有類庫的功能,例如下面代碼:

package cn.scala.xtwy

import java.io.File
import scala.io.Source
//RichFile類中定義了Read方法
class RichFile(val file:File){
  def read=Source.fromFile(file).getLines().mkString
}

object ImplicitFunction extends App{
  implicit def double2Int(x:Double)=x.toInt
  var x:Int=
  //隐式函數将java.io.File隐式轉換為RichFile類
  implicit def file2RichFile(file:File)=new RichFile(file)
  val f=new File("file.log").read
  println(f)
}
           

2 隐式轉換規則

隐式轉換可以定義在目标檔案中,例如:

implicit def double2Int(x:Double)=x.toInt
var x:Int = 
           

隐式轉換函數與目标代碼在同一檔案中,也可以将隐式轉換集中放置在某個包中,在使用的時候直接将該包引入即可,例如:

package cn.scala.xtwy
import java.io.File
import scala.io.Source
// 在cn.scala.xtwy包中定義了子包implicitConversion
// 然後在object ImplicitConversion中定義所有的隐式轉換方法
package implicitConversion{
  object ImplicitConversion{
    implicit def double2Int(x:Double) = x.toInt
    implicit def file2RichFile(file:File) = new RichFile(file)
  }
  class RichFile(val file:File){
    def read=Source.fromFile(file).getLines().mkString
  }

  object ImplicitFunction extends App{
    //在使用時引入所有的隐式方法
    import cn.scala.xtwy.implicitConversion.ImplicitConversion._
    var x:Int=


    val f=new File("file.log").read
    println(f)
}
           

這種方式在scala語言中比較常見,在前面我們也提到,scala會預設幫我們引用Predef對象中所有的方法,Predef中定義了很多隐式轉換函數,下面是Predef的部分隐式轉換源碼:

scala> :implicits -v
/* 78 implicit members imported from scala.Predef */
  /* 48 inherited from scala.Predef */
  implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A]
  implicit def any2Ensuring[A](x: A): Ensuring[A]
  implicit def any2stringadd(x: Any): runtime.StringAdd
  implicit def any2stringfmt(x: Any): runtime.StringFormat
  implicit def boolean2BooleanConflict(x: Boolean): Object
  implicit def byte2ByteConflict(x: Byte): Object
  implicit def char2CharacterConflict(x: Char): Object
  implicit def double2DoubleConflict(x: Double): Object
  implicit def float2FloatConflict(x: Float): Object
  implicit def int2IntegerConflict(x: Int): Object
  implicit def long2LongConflict(x: Long): Object
  implicit def short2ShortConflict(x: Short): Object

 //....................
           

那麼什麼時候會發生隐式轉換呢?主要有以下幾種情況:

1 當方法中參數的類型與實際類型不一緻時:

def f(x:Int)=x
//方法中輸入的參數類型與實際類型不一緻,此時會發生隐式轉換
//double類型會轉換為Int類型,再進行方法的執行
f()
           

2 當調用類中不存在的方法或成員時,會自動将對象進行隐式轉換:

package cn.scala.xtwy

import java.io.File
import scala.io.Source
//RichFile類中定義了Read方法
class RichFile(val file:File){
  def read=Source.fromFile(file).getLines().mkString
}

object ImplicitFunction extends App{
  implicit def double2Int(x:Double)=x.toInt
  var x:Int=
  //隐式函數将java.io.File隐式轉換為RichFile類
  implicit def file2RichFile(file:File)=new RichFile(file)
  //File類的對象并不存在read方法,此時便會發生隐式轉換
  //将File類轉換成RichFile
  val f=new File("file.log").read
  println(f)
}
           

前面講了什麼情況下會發生隐式轉換,下面我們講一講什麼時候不會發生隐式轉換:

1 編譯器可以不在隐式轉換情況下編譯通過,則不進行隐式轉換,例如:

// 這裡定義隐式轉換函數
scala> implicit def double2Int(x:Double) = x.toInt

// 下面幾條語句,不需要自己定義隐式轉換就可以編譯通過
// 是以它們不會發生隐式轉換
scala> *
res0: Double = 

scala> *
res1: Double = 

scala> *
res2: Double = 
           

2 如果轉換存在二義性,則不會發生隐式轉換,例如:

package implicitConversion{
  object ImplicitConversion{
    implicit def double2Int(x:Double)=x.toInt
    //這裡定義了一個隐式轉換
    implicit def file2RichFile(file:File)=new RichFile(file)
    //這裡又定義了一個隐式轉換,目的與前面那個相同
    implicit def file2RichFile2(file:File)=new RichFile(file)
  }

}

class RichFile(val file:File){
  def read=Source.fromFile(file).getLines().mkString
}

object ImplicitFunction extends App{
  import cn.scala.xtwy.implicitConversion.ImplicitConversion._
  var x:Int=

  //下面這條語句在編譯時會出錯,提示資訊如下:
  //type mismatch; found : java.io.File required:
  // ?{def read: ?} Note that implicit conversions
  //are not applicable because they are ambiguous:
  //both method file2RichFile in object
  //ImplicitConversion of type (file:
  //java.io.File)cn.scala.xtwy.RichFile and method
  //file2RichFile2 in object ImplicitConversion of
  //type (file: java.io.File)cn.scala.xtwy.RichFile
  //are possible conversion functions from java.io.File to ?{def read: ?}
value read is not a member of java.io.File

  val f=new File("file.log").read
  println(f)
}
           

3 隐式轉換不會嵌套進行,例如:

package cn.scala.xtwy
import java.io.File
import scala.io.Source

package implicitConversion{
  object ImplicitConversion{
    implicit def double2Int(x:Double)=x.toInt
    implicit def file2RichFile(file:File)=new RichFile(file)
    //implicit def file2RichFile2(file:File)=new RichFile(file)
    implicit def richFile2RichFileAnother(file:RichFile)=new RichFileAnother(file)
  }

}

class RichFile(val file:File){
  def read=Source.fromFile(file).getLines().mkString
}

//RichFileAnother類,裡面定義了read2方法
class RichFileAnother(val file:RichFile){
  def read2=file.read
}



object ImplicitFunction extends App{
  import cn.scala.xtwy.implicitConversion.ImplicitConversion._
  var x:Int=

  //隐式轉換不會多次進行,下面的語句會報錯
  //不能期望會發生File到RichFile,然後RifchFile到
  //RichFileAnthoer的轉換
  val f=new File("file.log").read2
  println(f)
}
           

3 隐式參數

在一般的函數資料定義過程中,需要明确傳入函數的參數,代碼如下:

package cn.scala.xtwy

class Student(val name:String){
  def formatStudent(outputFormat:OutputFormat) = {
    outputFormat.first + " " + this.name + " " + outputFormat.second
  }
}

class OutputFormat(val first:String, val second:String)

object ImplicitParamter {
  def main(args:Array[String]):Unit = {
    val outputFormat = new OutputFormat("<<", ">>")
    println(new  Student("john").formatStudent(outputFormat))
  }
}
// 執行結果
// <<john>>
           

如果給函數定義隐式參數的話,則在使用時可以不帶參數,代碼如下:

package cn.scala.xtwy

class Student(val name:String){
  // 利用柯裡化的定義方式,将函數的參數利用
  // implicit關鍵字辨別
  // 這樣的話,在使用的時候可以不給出implicit對應的參數
  def formatStudent()(implicit outputFormat:OutputFormat) = {
    outputFormat.first + " " + this.name + " " + outputFormat.second
  }
}

class OutputFormat(val first:String, val second:String)

object ImplicitParameter {
  def main(args:Array[String]) : Unit = {
    // 程式中定義的變量outputFormat被稱為隐式值
    implicit val outputFormat = new OutputFormat("<<", ">>")
    // 在.formatStudent()方法時,編譯器會查找類型為
    // outputFormat的隐式值,本程式定義的隐式值為outputFormat
    println(new Student("john").formatStudent())
  }
}
           

4 隐式參數中的隐式轉換

object ImplicitParameter extends App{
  // 指定T的上界為Ordered[T],所有混入特質Ordered
  // 的類都可以直接使用 `<` 符号比較
  def compare[T <: Ordered[T]](first:T,second:T) = {
    if(first < second) first else second
  }
}
           

我們還有一種方法可以達到上面的效果,就是通過隐式參數的隐式轉換來實作

object ImplicitParameter extends App {
  // 下面代碼中的(implicit order:T=>Ordered[T])
  // 給函數compare制定了一個隐式參數
  // 該隐式參數是一個飲食轉換
  def compare[T](first:T, second:T)(implicit order:T=>Ordered[T]) = {
    if(first > second) first else second
  }

  println(compare("A","B"))
}
           

5 函數中隐式參數使用概要

要點一:在定義函數時,如果函數沒有柯裡化,implicit關鍵字作用所有參數,例如:

// implicit 關鍵字在下面的函數中隻能出現一次
// 它作用于兩個參數x,y,也即x,y都是隐式參數
def sum(implicit x:Int, y:Int) = x + y
           

另外,值得注意的是,def

sum(implicit x:Int,y:Int)

在使用時,也隻能指定一個隐式值,即指定的隐式值分别對應函數中的參數(這裡是x,y)

def sum(implicit x: Int, y: Int) = x + y
//隻能指定一個隐式值
//例如下面下定義的x會自動對應maxFunc中的
//參數x,y即x=3,y=3,進而得到的結果是6
 implicit val x:Int=
//不能定義兩個隐式值
//implicit val y:Int=4
  println(sum)
           

要點二:要想使用implicit隻作用于某個函數參數,則需要将函數進行柯裡化,如:

def sum(x:Int)(implicit y:Int) = x + y
           

值得注意的是,下面這兩種帶隐式參數的函數也是不合法的

def sum(x:Int)(implicit y:Int)(d:Int) = x + y + d
def sum(x:Int)(implicit y:Int)(implicit d:Int) = x + y + d
           

要點三:匿名函數不能使用隐式參數,例如:

val sum2 = (implicit x :Int)=>x+
           

要點四:如果函數帶有隐式參數,則不能使用其偏函數,例如:

def sum(x: Int)(implicit y:Int)=x+y
//不能定義sum的偏函數,因為它帶有隐式參數
//could not find implicit value for
//parameter y: Int
//not enough arguments for method sum:
// (implicit y: Int)Int. Unspecified value parameter y.
def sum2=sum _
           

6 隐式轉換問題的梳理

1 多次隐式轉換問題

在上一講中我們提到,隐式轉換從源類型轉換到目标類型不會多次進行,也即源類到目标類型的轉換隻會進行一次

class RichFile(val file:File){
  def read = Source.fromFile(file).getLines().mkString
}
// RichFileAnother,裡面定義了read2方法
class RichFileAnother(val file:RichFile){
  def read2 = file.read
}
// 隐式轉換不會多次進行,下面的語句會報錯
// 不能期望會發生File到RichFile,然後RichFile到RichFileAnother的轉換
val f = new File("file.log").read2
println(f)
           

注意這裡指的是源類到目标類的轉換隻會進行一次,并不是說不存在多次隐式轉換,在一般的方法調用過程中可能會出現多次隐式轉換,例如:

class ClassA {
  override def toString() = "This is Class A"
}
class ClassB {
  override def toString() = "This is Class B"
}
class ClassC {
  override def toString() = "This is  ClassC"
  def printC(c: ClassC) = println(c)
}
class ClassD

object ImplicitWhole extends App {
  implicit def B2C(b:ClassB) = {
    println("B2C")
    new ClassC
  }
  implicit def D2C(d:ClassD) = {
    println("D2C")
    new ClassC
  }
  //下面的代碼會進行兩次隐式轉換
  //因為ClassD中并沒有printC方法
  //因為它會隐式轉換為ClassC(這是第一次,D2C)
  //然後調用printC方法
  //但是printC方法隻接受ClassC類型的參數
  //然而傳入的參數類型是ClassB
  //類型不比對,進而又發生了一次隐式轉地換(這是第二次,B2C)
  //進而最終實作了方法的調用
  new ClassD().printC(new ClassB)
}
           

還有一種情況會發生隐式轉換,如果給函數定義了隐式參數,在實際執行的過程中可能會發生多次隐式轉換,例如:

object Main extends App {
  class PrintOps() {
    def print(implicit i: Int) = println(i);
  }

  implicit def str2PrintOps(s: String) = {
    println("str2PrintOps")
    new PrintOps
  }

  implicit def str2int(implicit s: String): Int = {
    println("str2int")
    Integer.parseInt(s)
  }

  implicit def getString = {
    println("getString")
    "123"
  }
  //下面的代碼會發生三次隐式轉換
  //首先編譯器發現String類型是沒有print方法的
  //嘗試隐式轉換,利用str2PrintOps方法将String
  //轉換成PrintOps(第一次)
  //然後調用print方法,但print方法接受整型的隐式參數
  //此時編譯器會搜尋隐式值,但程式裡面沒有給定,此時
  //編譯器會嘗試調用 str2int方法進行隐式轉換,但該方法
  //又接受一個implicit String類型參數,編譯器又會嘗試
  //查找一個對應的隐式值,此時又沒有,是以編譯器會嘗試調用
  //getString方法對應的字元串(這是第二次隐式轉換,
  //擷取一個字元串,從無到有的過程)
  //得到該字元串後,再調用str2int方法将String類型字元串
  //轉換成Int類型(這是第三次隐式轉換)
  "a".print
}
           

參考博文

https://yq.aliyun.com/articles/60376?spm=5176.8251999.569296.18.3d10f3b6mLAnf

https://yq.aliyun.com/articles/60375?spm=5176.8251999.569296.19.3d10f3b6mLAnf