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