在學習Scala的時候,隱式轉換(implicit conversion)這個特性讓我實在是鬧不住啊。因而乎一邊試用一邊感慨:真的是太強大,太方便了。
不過,越是強大且方便的東西,越容易用出毛病來。在我不求甚解的狀況下,毛病就來了,我把它稱爲隱式轉換優先順序問題:
假設咱們有一個表示文本的行數的類LineNumber: 工具
class LineNumber ( val num : Int )
咱們能夠用這個類來表示一本書中每一頁的行數:
val lineNumOfPage1 = new LineNumber(112)
val lineNumOfPage2 = new LineNumber(120)
上面的代碼分別表示了第一頁和第二頁的行數。固然,咱們也應該能夠將它們相加,獲得這兩頁的總行數:
val totalLineNum = lineNumOfPage1 + lineNumOfPage2
這樣的話,咱們的LineNumber類裏就應該有一個 「+」 方法來將兩個對象相加:
class LineNumber ( val num : Int ) {
def + ( that : LineNumber ) = new LineNumber( this.num + that.num )
}
從直觀上講,咱們甚至能夠直接用整數來和LineNumber相加:
val totalLineNumber = lineNumOfPage1 + 120
//或者
val totalLineNumber = 112 + lineNumOfPage2
寫到這裏咱們發現,LineNumber對象中沒有接受整型做爲參數的 「+」 方法;而整型中也不會有接受LineNumber做爲參數的 「+」 方法。
爲了說明問題,咱們不在LineNumber類中增長一個整型做爲參數的重載方法,而是定義兩個隱式轉換,一個將Int轉換爲LineNumber,另外一個將LineNumber轉換爲Int:
object LineNumber{
implicit def intToLineNumber( i : Int ) = new LineNumber(i)
implicit def lineNumberToInt( o : LineNumber ) = o.num
}
接下來再讓咱們嘗試用LineNumber和整型相加:
import LineNumber._
val totalLineNumber1 = lineNumOfPage1 + 120
val totalLineNumber2 = 112 + lineNumOfPage2
咱們會獲得什麼結果呢?
我最初認爲,Scala編譯器可能會產生編譯錯誤,畢竟咱們如今的代碼中對於兩種類型的隱式轉換都是存在的,這樣的話上面兩個語句的方法參數和調用方法的對象均可以作隱式轉換。很明顯我錯了。運行結果:totalLineNumber1是一個新的LineNumber的對象,而且它的字段num會是232;而totalLineNumber2則是一個Int類型,值爲232。
先讓咱們看看在編譯時加入 「-Xprint:typer」 後上面兩句的結果:
val totalLineNumber1: LineNumber = lineNumOfPage1.+(LineNumber.intToLineNumber(120));
val totalLineNumber2: Int = 112.+(LineNumber.lineNumberToInt(lineNumOfPage2));
到這裏已經很清楚了。Scala編譯器優先選擇了方法的參數做爲轉換對象,而沒有選擇調用方法的對象。這是爲何呢?
其實,當程序類型檢查出現問題的時候,Scala編譯器會在兩個位置上對出現問題的語句嘗試使用隱式轉換:
- 第一個位置是 「能夠直接將某個類型轉換爲指望的類型」 的地方
- 第二個位置是 「能夠將調用方法的對象轉換爲合適類型」 的地方
第一個位置是那種咱們須要A可是傳入的倒是B的地方。上面的例子正好就是這樣的狀況,對於totalLineNumber1來講,lineNumOfPage1的 「+」 方法須要的參數類型是LineNumber型,然而咱們傳入的是Int類型,Scala編譯器在這裏檢測到類型錯誤後,會尋找將Int轉換爲LineNumber的隱式轉換。由於在代碼開始時,咱們利用 "import LineNumber._ " 導入了相應的隱式轉換,所以Scala編譯器使用導入的隱式轉換將整型120轉換爲了LineNumber類型。totalLineNumber2發生的狀況是相同的,只不過是將LineNumber類型轉換爲了Int類型。
也就是說,Scala編譯器在使用隱式轉換的時候,首先回去尋找能夠直接轉換類型的位置,其次纔對調用方法的對象。若是咱們將咱們的隱式轉換中intToLineNumber的轉換去掉,那麼咱們獲得的totalLineNumber1就會變爲一個整型。
就像我開頭所說的那樣,Scala的隱式轉換是一個很是強大的工具,可是若是不瞭解它,或者將其濫用在程序中,會使得程序難以理解和維護。真的到了那個時候,就確實「鬧不住」了。