[譯]理解Go的reflect

原文:medium.com/better-prog…golang

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.gospa

三個使用reflect的場景

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

  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也讓咱們擁有了一部分動態語言的特性,你能夠很容易獲取參數的類型以及值,在使用它的時候。

相關文章
相關標籤/搜索