1. 程式人生 > >Scala入門到精通——第十八節 隱式轉換與隱式引數(一)

Scala入門到精通——第十八節 隱式轉換與隱式引數(一)

本節主要內容

  1. 隱式轉換簡介
  2. 隱式轉換函式
  3. 隱式轉換規則
  4. 隱式引數

1. 隱式轉換簡介

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

在scala語言中,隱式轉換是無處不在的,只不過scala語言為我們隱藏了相應的細節,例如scala中的類繼承層次結構中:
這裡寫圖片描述
它們存在固有的隱式轉換,不需要人工進行干預,例如Float在必要情況下自動轉換為Double型別

在前一講的檢視界定中我們也提到,檢視界定可以跨越類層次結構進行,它背後的實現原理就是隱式轉換,例如Int型別會檢視界定中會自動轉換成RichInt,而RichInt實現了Comparable介面,當然這裡面的隱式轉換也是scala語言為我們設計好的
這裡寫圖片描述

本節將對隱式轉換中的隱式轉換函式、隱式轉換規則、隱式引數進行介紹,使大家明白如何自己實現隱式轉換操作。

2. 隱式轉換函式

下列賦值如果沒有隱式轉換的話會報錯:

scala> val x:Int=3.5
<console>:7: error: type mismatch;
 found   : Double(3.5)
 required: Int
       val x:Int=3.5
                 ^

新增隱式轉換函式後可以實現Double型別到Int型別的賦值

//定義了一個隱式函式double2Int,將輸入的引數
//從Double型別轉換到Int型別
scala> implicit def double2Int(x:Double)=x.toInt warning: there were 1 feature warning(s); re-run with -feature for details double2Int: (x: Double)Int //定義完隱式轉換後,便可以直接將Double型別賦值給Int型別 scala> val x:Int=3.5 x: Int = 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=3.5
  //隱式函式將java.io.File隱式轉換為RichFile類
  implicit def file2RichFile(file:File)=new RichFile(file)
  val f=new File("file.log").read
  println(f)
}

3. 隱式轉換規則

隱式轉換可以定義在目標檔案當中,例如

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

隱式轉換函式與目的碼在同一個檔案當中,也可以將隱式轉換集中放置在某個包中,在使用進直接將該包引入即可,例如:

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=3.5


  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(3.14)

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=3.5
  //隱式函式將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
warning: there were 1 feature warning(s); re-run with -feature for details
double2Int: (x: Double)Int

//下面幾條語句,不需要自己定義隱式轉換編譯就可以通過
//因此它不會發生前面定義的隱式轉換
scala> 3.0*2
res0: Double = 6.0

scala> 2*3.0
res1: Double = 6.0

scala> 2*3.7
res2: Double = 7.4

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=3.5

  //下面這條語句在編譯時會出錯,提示資訊如下:
  //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)
}

編譯提示隱式轉換存在二義性(ambiguous)

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=3.5

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

理解了這些規則之後,在使用隱式轉換時才能夠得心應手

4. 隱式引數

在一般的函資料定義過程中,需要明確傳入函式的引數,程式碼如下:

package cn.scala.xtwy

class Student(var name:String){
  //將Student類的資訊格式化列印
  def formatStudent(outputFormat:OutputFormat)={
    outputFormat.first+" "+this.name+" "+outputFormat.second
  }
}

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

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

如果給函式定義隱式引數的話,則在使用時可以不帶引數,程式碼如下:

package cn.scala.xtwy
class Student(var name:String){
  //利用柯里化函式的定義方式,將函式的引數利用
  //implicit關鍵字標識
  //這樣的話,在使用的時候可以不給出implicit對應的引數
  def formatStudent()(implicit outputFormat:OutputFormat)={
    outputFormat.first+" "+this.name+" "+outputFormat.second
  }
}

class OutputFormat(var 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())
  }
}

新增公眾微訊號,可以瞭解更多最新Spark、Scala相關技術資訊
這裡寫圖片描述