包括to_s
和to_str
、to_i
和to_int
、to_a
和to_ary
、to_h
和to_hash
。統稱為to_x
和to_xxx
。
那么,to_x
和to_xxx
的區別是什么,什么時候使用to_x
,什么時候使用to_xxx
。
解釋
使用鴨子模型來解釋比較容易點。
只要像鴨子,就能當成鴨子,這就是to_x
。只有它真的是鴨子,才能當成鴨子,這就是to_xxx
。
以to_s
和to_str
為例。
所有對象都能使用to_s
方法,用來將對象以字符串的格式去描述、去輸出。也就是說,所有對象都能使用字符串的描述格式。
# 任意對象都能直接使用to_s()去描述自身
>> Object.new.to_s
=> "#<Object:0x00000002272e58>"
# 數值類中重寫了to_s(),使之轉換成字符串格式的數值描述形式
>> 1.to_s
=> "1"
只有真的是字符串的對象,或者能完全扮演字符串的對象,才有必要去使用to_str
。例如,String類自身、String類的某些子類,它們是真的鴨子,並不是簡單的像鴨子。也就是說,只有嚴格符合鴨子要求的類型,才可以考慮去定義to_str
。
再嚴格一點,當某個地方能使用String類對象的時候,也一定能使用某類對象時(比如String的部分子類),這類對象就可以考慮去使用to_str
。
>> 1.to_str
NoMethodError: undefined method `to_str' for 1:Fixnum
>> Object.new.to_str
NoMethodError: undefined method `to_str' for #<Object:0x00000002267648>
或者說,to_x
是輸出出來給人讀的,to_xxx
是讓程序健壯的,讓你在不理解的情況下別亂定義to_xxx
。
to_i
和to_int
、to_a
和to_ary
、to_h
和to_hash
也都一樣,to_x
是寬泛程度的數據類型轉換,to_xxx
是嚴格的、必須知道是干什么的時候才進行的數據類型轉換。
示例分析
例如:
>> [1, 2].join(',')
=> "1,2"
>> [1, 2].join(1)
TypeError: no implicit conversion of Fixnum into String
數組的join()
方法用來將數組轉換成字符串,且使用連接字符進行連接。也就是說,數組中的每個元素以及連接符自身都得轉換成字符串,才能保證轉換的結果是字符串。
對於數組自身而言,調用to_s()
即可將其內所有元素轉換成字符串格式,但是連接符不能隨便轉換,只有那些能夠作為連接符的類型才能轉換,例如這里的數值1不能作為連接符,所以應當讓連接符的轉換過程使用to_str()
,保證程序的健壯性、安全性。當然,如果你認為1也可以作為連接符,你可以在設計join()程序的時候,通過to_s()
去轉換這里的數值1,但關鍵是join()不是你寫的,而是別人寫的,別人這么寫有他自己的考慮。
再例如to_a
和to_ary
,將hash結構轉換成array:
>> {a: 10}.to_a
=> [[:a, 10]]
>> {a: 10}.to_ary
NoMethodError: undefined method `to_ary' for {:a=>10}:Hash
Did you mean? to_a
上面第一個轉換能成功。因為寫hash類型的程序員認為,hash可以以一種方式轉換成數組類型,於是它在hash類中定義了to_a()
。這個轉換並不影響大局,僅僅只是實現一個簡單的功能而已。
而to_ary()
轉換失敗,因為hash是hash,array是array,在能使用array的地方,不代表能使用hash,假如在hash中定義了to_ary
,那么在很大意義上就意味着hash和array在很多地方可以互換使用(特指hash能替代array),也就是能使用array的地方很可能也應該允許它使用hash。當然,僅僅只是意義上的替換,而非真正的能替換,但這很可能會牽一發而動全身。
再例如,浮點數肯定可以使用to_i
簡單轉化成整數類型,但它應該定義to_int()
嗎?如果編寫Float類的程序員認為,浮點數就是浮點數,絕不能當成int對象,那么他就要保證float對象不能轉換成int,這時就不要定義to_int
。但如果他認為浮點數作為一種int使用,那么就應該定義to_int
。事實上,Float類中to_i
和to_int
都定義了。
>> a=3.5
>> a.class # => Float
>> a.to_i # => 3
>> a.to_int # => 3
結論
分為兩種情況:什么時候調用to_x
和to_xxx
,以及什么時候在自己的類中實現to_x
和to_xxx
。
- 什么時候調用的問題
- 調用
to_x
來將你的類做個寬松的類型轉換 - 調用
Cls.to_xxx(arg)
來驗證arg真的能充當Cls使用
- 調用
- 什么時候實現的問題
- 實現
to_x
,只要你認為可以按照你的觀點轉換將你的類轉換成某個類型 - 實現
Cls.to_xxx(arg)
,只有當前想要保證某arg對象真的可以充當Cls時定義
- 實現
最后,基本上所有類都可以按照你自己的想法去定義to_x
,但是很少定義to_xxx
,除非你真的知道自己在干什么,知道這會造成什么結果。
參考鏈接:to_s vs. to_str (and to_i/to_a/to_h vs. to_int/to_ary/to_hash) in Ruby