1. 程式人生 > 程式設計 >[譯]理解Go的reflect

[譯]理解Go的reflect

原文:medium.com/better-prog…

Go是一個強型別的靜態程式語言。然而,一些Go的特性讓它看起來又像是一門動態語言。例如,如果你不確定你接收的引數的型別,你可以使用interface來接收所有型別的引數傳遞。

記住只有interface是有reflect屬性的

我們注意到interface允許Go實現多型。沒有任何一種型別是特別需要強調的。可以是string int64 float32 甚至是集合(array/map)。但計算機執行這些程式碼時候,reflect幫助檢查,修改其自身的結構與行為。這個過程允許我們知道物件的型別以及記憶體在執行時的結構。

我們為什麼需要reflect?

  • 允許提前定義引數型別(通常發生在暴露的API上)
  • 函式能根據傳參動態執行

reflect的缺點

  • 影響程式碼可讀性
  • 遮蔽了程式碼編譯時的錯誤檢查。作為動態語言,Go的編譯器可以提前檢測資料型別的錯誤,在編譯的時候。當資料在interface中沒有特性指明型別的時候,伺服器會有在執行程式碼時候出現panic的風險
  • 降低了整體的效能。使用reflect需要服務端去做額外的工作去獲取引數的值以及具體的型別,因此,儘量避免在一些很重要的引數上使用interface

reflect的兩個基礎功能

reflect兩個主要功能是reflect.Type以及reflect.Value

簡單的說reflect.Type

提供引數的實際型別,當reflect.Value結合_type data一起使用的時候可以允許開發者讀取或改寫引數的值。

func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
複製程式碼

然後你可以使用fmt.Printf()%T來將引數進行格式化來或者reflect.TypeOf的結果,如下:

fmt.Printf("%T",3) //int
複製程式碼

reflect.TypetoType是一個改變資料型別的方法:

func toType(t * rtype) Type {
    if t == nil {
    return
nil } return t } 複製程式碼

換句話說,reflect.Value返回一個儲存在interface{}中的變數。已經有很多方法包含如SetLen(n int)SetMapIndex(key,val Value),Int(),TrySend(x refelect.Value)等等。在完整版的檔案上,可以參考src/reflect/value.go

三個使用reflect的場景

來自Go官方網站的使用場景:

  1. 從interface到reflect物件
  2. 從reflect物件到interface
  3. 改變一個interface,但它的值必須是可被改變的

經典的例子如下:

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic
複製程式碼

伺服器在執行這段程式碼時候會panic,因為v並不是x,而是x的靠背。因此,所有對於v的修改都是禁止的。

所以,我們需要使用指標來解決這個問題

var x float64 = 3.4
y := reflect.ValueOf(&x)

fmt.Println(“type of y”,y.Type()) // *float64
fmt.Println(“settability of y:”,y.CanSet()) // false
複製程式碼

這時候y仍然不能代替x,你可以通過y.Elem()來修改

z := y.Elem()
z.SetFloat(7.1)

fmt.Println(z.Interface()) // 7.1
fmt.Println(x) // 7.1
複製程式碼

可以注意到指標會對所指向的值一併作出修改,也就是x

reflect的應用

reflect被廣泛應用到物件序列化,fmt相關函式,以及ORM等等上。

JSON序列化

在Go中有兩個序列化與反序列化的函式

func Marshal(v interface{})([]byte,error)
func Unmarshal(data []byte,v interface{}) error
複製程式碼

兩個函式都接收interface型別作為引數,因此在我們執行函式內部時需要知道引數的值以及型別的時候,reflectget set方法就能起到作用了

DeepEqual函式

在測試一個功能的時候,我們往往需要知道兩個變數是否完全一致。例如,判斷一個slice中所有的元素是否相同或者檢查兩個map中所有的key對應的value是否相同。這時就需要DeepEqual函式

func DeepEqual(x,y interface{}) bool
複製程式碼

DeepEqual接收兩個interface的引數。你可以傳入任意值,它會返回一個布林值表示傳入的兩個引數是否完全相等。

等一下,什麼叫做 deeply 相等,看看下面例子

type FirstInt int
type SecondInt int

func main() {
    m := FirstInt(1)
    n := SecondInt(1)
    fmt.Println(reflect.DeepEqual(m,n)) // false
}
複製程式碼

在上面例子中雖然m,n都是1,但是他們的資料型別是不一樣的,一個是FirstIn型別,一個是SecondInt型別。所以它們是不相等的。

總結

Go作為一門靜態語言,我們可以非常明確在語言編寫的彈性上來說相比於例如Python這樣的動態語言來說肯定是有侷限性的。但是通過使用reflect也讓我們擁有了一部分動態語言的特性,你可以很容易獲取引數的型別以及值,在使用它的時候。