深入Go語言 - 13 反射

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

目錄 [−]

  1. Type
  2. Value
  3. 轉換

本章重點介紹Go語言中的反射。
包reflect可以實現運行時的反射,允許程式操縱對象的值和類型。
典型地,你可以擷取 interface{}的動態類型以及的它的值和方法。

Go是靜態類型的語言,每一個對象在聲明和初始化的時候都已經有一個確定值,即使是聲明為介面類型的變數,它的靜態類型也已經確定,即使任何包含這個介面方法集的類型的對象都可以賦值給它。

我們可以在運行時擷取對象的動態類型和值。

類型Type和值Value是我們使用發射庫的主要用的兩個概念。

Type

Type是一個interface,代表Go中的一個類型,可以把它看成某個類型的中繼資料(描述類型的類型),這個類型既可以是Go語言或者庫中定義的type類型,也可以你自己定義的type類型。

下面我們介紹它的主要方法以及一些輔助方法。

值得注意的是,並不是所有的方法都對某種類型有效,比如有些方法只對函數類型有意義,有的只對Struct有意義。如果對某個類型調用了錯誤的方法,則會發生運行時的panic,比如針對struct Type,調用IsVariadic方法。所以最好先判斷一下Type的Kind。

首先我們先為下面的例子定義一個簡單的struct用來測試, Bird有三個欄位Field和三個方法,其中兩個欄位和兩個方法是匯出的:

1234567891011121314151617
type Bird struct {Name  stringColor stringage   int}func (b Bird) Sing() string {return "sing"}func (b *Bird) Fly() string {return "fly"}func (b *Bird) food() {}

TypeOf返回一個介面對象的動態類型,也就是類型reflect.Type的一個值:

123
var bird = Bird{Name: "parrot", Color: "blue"}var t reflect.Typet = reflect.TypeOf(bird)
  • NamePkgPathString
    返回動態類型的名稱,包名以及字串表示。
123
fmt.Println(t.Name()) //Birdfmt.Println(t.String()) //main.Birdfmt.Println(t.PkgPath()) //main

Name傳回型別名,如Bird,對於未定義名稱的類型,返回Null 字元串。

PkgPath返回包名,代表這個包的唯一識別碼,所以可能是單一的包名,或者encoding/base64。對於Go內建的類型string,error等,或者未定義名稱的類型struct{}等,則返回Null 字元串。

String方法返回這個類型的字串表示,包名可能用簡寫表示。

  • Kind
    傳回型別的分類。 你自己定義的類型必定屬於下面的某一類:
1234567891011121314151617181920212223242526272829
const (        Invalid Kind = iota        Bool        Int        Int8        Int16        Int32        Int64        Uint        Uint8        Uint16        Uint32        Uint64        Uintptr        Float32        Float64        Complex64        Complex128        Array        Chan        Func        Interface        Map        Ptr        Slice        String        Struct        UnsafePointer)

例如:

1
fmt.Println(t.Kind()) //struct
  • SizeBitsAlignFieldAlign
12345
fmt.Println(t.Size()) //40//fmt.Println(t.Bits())       //panicfmt.Println(t.Align())      //8fmt.Println(t.FieldAlign()) //8}

Size返回儲存這個類型的一個值所需要的位元組數,它返回要儲存儲這個值需要的記憶體,而不會計算它引用的記憶體的大小。 比如字串是以StringHeader類型來儲存的,它包含一個指標指向記憶體字元儲存的資料,它的size只計算儲存這個結構所需的記憶體,而不會計算指標指向的資料佔用的位元組數的,資料會對齊的。

Bits傳回型別的size,以bit計算,但是如果類型不是Int、Uint、Float、Complex之一則panic。

Algin是這個類型的一個變數在記憶體中的對齊後的所用的位元組數。FieldAlign指這種類型的變數如果是struct中的欄位,那麼它對齊後所用的位元組數。

對於gc編譯器來講,AlignFieldAlign是一樣的,但是對於gccgo來講,它們可能不同。

還有下面會講到的reflect.StructField.Offset,它是某個欄位在struct中的位移量,一起考慮了前面欄位的對齊。

為什麼要對齊?
第一個原因——很多CPU只從對齊的地址開始載入資料,而有的CPU這樣做,只是更快一點。
第二個原因——外部匯流排從記憶體一次擷取的資料往往不是1byte,而是4bytes或許8bytes,或者更多~~
引子知乎

注意Size是值佔用的位元組數,而Align是變數占的位元組數。

比如在我當前的開發環境下(64-bit windows 10), 字串的Size為16個位元組,而字串的Align為8, int類型的SizeAlign都是8, int32的SizeAlign都是4。

  • ImplementsAssignableToConvertibleToComparable

Implements傳回型別是否實現了某個介面,如果參數的類型不是interface類型,則會panic。

123456
var r *io.Readert1 := reflect.TypeOf(r).Elem()t2 := reflect.TypeOf(&os.Stdout).Elem()fmt.Println(t.Implements(t1))  //falsefmt.Println(t2.Implements(t1)) //true

AssignableTo一個類型的值是否可以賦值給參數指定的類型,下面的例子中Bird類型的指標對象可以賦值給IBird介面:

1234567
type IBird interface {Sing() stringFly() string}var i IBird = &birdfmt.Println(t.AssignableTo(reflect.TypeOf(i).Elem()))

ConvertibleTo 一個類型的值是否可以轉換成另一個類型的值:

12345
type Bird2 Birdvar bird2 Bird2t3 := reflect.TypeOf(bird2)fmt.Println(t3.ConvertibleTo(t)) //truebird = Bird(bird2)               //可以轉換

Comparable傳回型別是否可以比較:

1
fmt.Println(t.Comparable()) //true

類型如果可以比較,就可以使用==!=運算子。可以參看 比較子。

  • NumFieldFieldFieldByIndexFieldByNameFieldByNameFunc
    這一組方法用來擷取Struct的Field的資訊,這個資訊通過StructField類型來描述。如果類型不是Struct,調用相應的方法導致運行時panic。

NumField返回Struct的欄位的數量。

1
fmt.Println(t4.NumField()) //1

Field返回struct的第i個欄位的資訊,包括未匯出對象的資訊

123
fmt.Println(t.Field(0)) //{Name  string  0 [0] false}fmt.Println(t.Field(1)) //{Color  string  16 [1] false}fmt.Println(t.Field(2)) //{age github.com/smallnest/dive-into-go/ch12/model int  32 [2] false}

FieldByIndex對於嵌套的Struct,可以遞迴地得到某一層的欄位的資訊:

12345678910111213141516
type S1 struct {Name stringAge  int}type S2 struct {S1}type S3 struct {S2}var s S3t4 := reflect.TypeOf(s)fmt.Println(t4.FieldByIndex([]int{0, 0, 1})) //{Age  int  16 [1] false}

這個例子S3嵌套S2,S2嵌套S1,我們通過0,0,1就可以得到S1的欄位Age的資訊。

FieldByName根據名稱得到欄位的資訊:

1
fmt.Println(t4.FieldByName("Name")) //{Name  string  0 [0 0 0] false} true

FieldByNameFunc根據一個函數篩選欄位,返回第一個合格欄位:

1234567
fmt.Println(t4.FieldByNameFunc(func(n string) bool {if n == "Age" {return true}return false}))
  • NumMethodMethodMethodByName
    這是一組操作類型的方法的一組方法。

對於非介面的類型T或者*T,它的方法類型和函數欄位描述了方法的第一個參數就是receiver。

對於介面類型,它的方法類型的Func欄位總是為nil。

NumMethod返回這個類型的方法集中方法的數量, Method返回第i個方法的資訊:

1234567
fmt.Println(t.NumMethod())  //1fmt.Println(t.Method(0))    //{Sing  func(model.Bird) string <func(model.Bird) string Value> 0}t5 := reflect.TypeOf(&bird)fmt.Println(t5.NumMethod()) //3fmt.Println(t5.Method(0)) //{Fly  func(*model.Bird) string <func(*model.Bird) string Value> 0}fmt.Println(t5.Method(1)) //{Sing  func(*model.Bird) string <func(*model.Bird) string Value> 1}fmt.Println(t5.Method(2)) //{food github.com/smallnest/dive-into-go/ch12/model func(*model.Bird) <func(*model.Bird) Value> 2}

MethodByName則是根據名稱尋找方法。

  • NumInInNumOutOutIsVariadic
    對於函數類型,我們關注的是它的輸入參數資訊Int和輸出參數資訊Out
    IsVariadic返回函數是否是變參的。
1234567
f := http.ListenAndServeft := reflect.TypeOf(f)fmt.Println(ft.NumIn())  //2fmt.Println(ft.In(0))    //stringfmt.Println(ft.NumOut()) //1fmt.Println(ft.Out(0))   //errorfmt.Println(ft.IsVariadic()) //false
  • Key
    Key方法返回map類型的key的類型,如果不是map類型,則調用此方法會panic。
123
var m map[string]intmt := reflect.TypeOf(m)fmt.Println(mt.Key())
  • Elem
    Elem方法傳回型別 Array, Chan, Map, Ptr, Slice的元素的類型。

對於Map類型,它返回的值的類型。

對於指標類型Ptr,它返回指標指向的元素的類型。

對於Chan,它返回傳遞的元素的類型。

數組和Slice返回的是它包含的元素的類型。

  • Len
    Len返回數組的長度,因為數組的類型中包含長度的定義。如果不是數組則會panic。

  • ChanDir
    返回channel的方向, chan、chan<-、<-chan

Value

Value描述對象的值資訊,同樣,並不是所有的方法對任何的類型都有意義,特定的方法只適用於特定的類型。

零值代表沒有值,它的IsValid總是返回false,它的Kind總是返回Invalid,它的String總是返回""。

A Value can be used concurrently by multiple goroutines provided that the underlying Go value can be used concurrently for the equivalent direct operations.

Using == on two Values does not compare the underlying values they represent, but rather the contents of the Value structs. To compare two Values, compare the results of the Interface method.

本文中不準備詳細闡述Value對象的方法。不是這些方法不重要,而是它的方法太多了。

它針對每一中類型的操作都提供了相應的方法,比如slice類型,有Append、AppendSlice等方法、Map有MapIndex、MapKeys等方法、Struct有Field、FieldByIndex等方法。 注意Value的Field方法返回的是欄位的實值型別Value,而不是欄位的類型描述Type。還有針對數值型、Bool型、Channel類型的方法等。

Indirect返回指標指向的對象,如果不是指標,則返回參數本身。

Addr返回一個可定址的對象的指標。 只有slice的元素、可定址的數組的元素、可定址的struct的欄位、指標的可定址的結果才可以調用Addr方法。可以通過CabAddr檢查。可以查看Go規範中的描述:Address operators。

Zero傳回型別的零值。

一組SetXxx方法用來設定值的值(好繞口)。

下面是一組輔助函數,產生Value對象:
MakeChanMakeFuncMakeMapMakeSliceNewNewAt產生某個類型的值Value。
ValueOf從介面對象返回Value。

轉換

1、 從介面對象到反射對象

123
var x float64 = 3.4  fmt.Println("type:", reflect.TypeOf(x))fmt.Println("value:", reflect.ValueOf(x))

2、從反射對象到介面對象

1
func (v Value) Interface() interface{}

如果想轉為特定的類型的對象,可以用type assertion:

1
y := v.Interface().(float64)

3、修改反射對象
對象的值必須是可設定的,可以用CanSet方法判斷。

123456
var x float64 = 3.4p := reflect.ValueOf(&x)v := p.Elem()v.SetFloat(7.1)fmt.Println(v.Interface())fmt.Println(x)

也有一些第三方的簡化go reflect的庫,如go-reflector。

反射經常用在序列化和反序列的實現中,如官方的json、xml庫。

但是請記住一點,發射的效能並不高,所以很多序列化庫採用代碼模版的方式產生Model對象,而不是反射的方式序列化和還原序列化對象。

參考

  • http://blog.golang.org/laws-of-reflection
  • http://golang.org/pkg/reflect/
  • http://research.swtch.com/interfaces
  • http://my.oschina.net/tongjh/blog/513540
  • http://blog.altoros.com/golang-internals-part-6-bootstrapping-and-memory-allocator-initialization.html
  • http://grokbase.com/t/gg/golang-nuts/153kv99728/go-nuts-how-to-use-reflect-type-implements

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.