Go 反射實踐及剖析

legendtkl發表於2016-12-08

Go struct拷貝

在用Go做orm相關操作的時候,經常會有struct之間的拷貝。比如下面兩個struct之間要拷貝共同成員B,C。這個在struct不是很大的時候從來都不是問題,直接成員拷貝即可。但是當struct的大小達到三四十個成員的時候,就要另闢蹊徑了。

做法一. 反射

做法二. json

先encode成json,再decode,其實golang的json包內部實現也是使用的反射,所以再大型專案中可以考慮使用ffjson來作為替代方案。

關於golang中的反射機制一直是大家詬病挺多的。因為反射中使用了型別的列舉,所以效率比較低,在高效能場景中應該儘量規避,但是,對於大部分應用場景來說,犧牲一點效能來極大減少程式碼行數,或者說提高開發效率都是值得的。

Reflect

關於Reflect的介面可以參考golang的文件,也可以直接看go的原始碼。reflect的核心是兩個,一個是Type,一個是Value。reflect的使用一般都是以下面語句開始。

Value

Value的定義很簡單,如下。

下面針對object的型別不同來看一下reflect提供的方法。

built-in type

reflect針對基本型別提供瞭如下方法,以Float()為例展開。

可以看到內部Float內部實現先通過kind()判斷value的型別,然後通過指標取資料並做型別轉換。kind()的定義如下

flag是Value的內部成員,所以方法也被繼承過來。通過實現不難猜到reflect對不同的型別是通過一個整數來實現的。我們來驗證一下,在type.go檔案中找到Kind定義,注意這個地方Kind()只是一個型別轉換。

緊挨著Kind定義的下面就是各種型別的表示:Bool=1, Int=2,…再來看一下f & flagKindMask的意思。再value.go檔案中找到flagKindMask的定義:

所以這句f & flagKindMask 的意思就是最f的低5位,也就是對應了上面的各種型別。

上面說完了資料的讀介面,其實寫介面也很類似。唯一的區別在於reflect還提供了一個Set()方法,就是說我們自己去保證資料的型別正確性。

Struct

其實使用反射的很多場景都是struct。reflect針對struct提供的函式方法如下:

通過上面的方法不出意外就可以取得對應是struct field了。

其他型別:Array, Slice, String

對於其他型別,reflect也提供了獲得其內部成員的方法。

函式呼叫

reflect當然也可以實現函式呼叫,下面是一個簡單的例子。

當然我們還可以通過struct來呼叫其方法,需要注意的一點是通過反射呼叫的函式必須是外部可見的(首字母大寫)。

總結一下reflect提供了函式呼叫的相關介面如下:

Type

Type是反射中另外重要的一部分。Type是一個介面,裡面包含很多方法,通常我們可以通過reflect.TypeOf(obj)取得obj的型別對應的Type介面。介面中很多方法都是對應特定型別的,所以呼叫的時候需要注意。

另外reflect包還為我們提供了幾個生成特定型別的Type介面的方法。這裡就不一一列舉了。

小結

關於reflect提供的介面需要注意的一點就是,一定要保證型別是匹配的,如果不匹配將導致panic。關於Value的主要介面都在這,本來還想寫一下Type以及內部的實現機制的,只能放到下篇再寫了。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

Go 反射實踐及剖析 Go 反射實踐及剖析

相關文章