Golang 學習筆記(11)—— 反射

轉載請注明出處:Golang 學習筆記(11)—— 反射

Golang

介紹

反射是程序執行時檢查其所擁有的結構。尤其是類型的一種能力。這是元編程的一種形式。它同一時候也是造成混淆的重要來源。

每一個語言的反射模型都不同(同一時候很多語言根本不支持反射)。本節將試圖明白解釋在 Go 中的反射是怎樣工作的

實現

package

import "reflect"

Type和Value

首先,reflect包有兩個數據類型我們必須知道,一個是Type,一個是Value。

Type就是定義的類型的一個數據類型,Value是值的類型

下面分別對Type和Value做個簡單介紹一下

Type

Type類型用來表示一個go類型。

不是所有go類型的Type值都能使用所有方法。請參見每個方法的文檔獲取使用限制。在調用有分類限定的方法時,應先使用Kind方法獲知類型的分類。調用該分類不支持的方法會導致運行時的panic。

獲取Type對象的方法:

func TypeOf(i interface{}) Type

例如:

str := "this is string"
type := reflect.TypeOf(str)

Type中常用的方法:

  • Kind() Kind
    Kind返回該接口的具體分類
    Kind代表Type類型值表示的具體分類。零值表示非法分類。
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
)
  • Name() string
    Name返回該類型在自身包內的類型名,如果是未命名類型會返回""

  • PkgPath() string
    PkgPath返回類型的包路徑,即明確指定包的import路徑,如"encoding/base64"
    如果類型為內建類型(string, error)或未命名類型(*T, struct{}, []int),會返回""

  • NumField() int
    返回struct類型的字段數(匿名字段算作一個字段),如非結構體類型將panic

  • Field(i int) StructField
    返回struct類型的第i個字段的類型,如非結構體或者i不在[0, NumField())內將會panic

  • FieldByIndex(index []int) StructField
    返回索引序列指定的嵌套字段的類型,
    等價于用索引中每個值鏈式調用本方法,如非結構體將會panic

  • FieldByName(name string) (StructField, bool)
    返回該類型名為name的字段(會查找匿名字段及其子字段),
    布爾值說明是否找到,如非結構體將panic

  • type StructField

type StructField struct {
    // Name是字段的名字。PkgPath是非導出字段的包路徑,對導出字段該字段為""。
    Name    string
    PkgPath string
    Type      Type      // 字段的類型
    Tag       StructTag // 字段的標簽
    Offset    uintptr   // 字段在結構體中的字節偏移量
    Index     []int     // 用于Type.FieldByIndex時的索引切片
    Anonymous bool      // 是否匿名字段
}

StructField類型描述結構體中的一個字段的信息。

  • NumMethod() int
    返回該類型的方法集中方法的數目
    匿名字段的方法會被計算;主體類型的方法會屏蔽匿名字段的同名方法;
    匿名字段導致的歧義方法會濾除

  • Method(int) Method
    返回該類型方法集中的第i個方法,i不在[0, NumMethod())范圍內時,將導致panic
    對非接口類型T或*T,返回值的Type字段和Func字段描述方法的未綁定函數狀態
    對接口類型,返回值的Type字段描述方法的簽名,Func字段為nil

  • MethodByName(string) (Method, bool)
    根據方法名返回該類型方法集中的方法,使用一個布爾值說明是否發現該方法
    對非接口類型T或*T,返回值的Type字段和Func字段描述方法的未綁定函數狀態
    對接口類型,返回值的Type字段描述方法的簽名,Func字段為nil

  • type Method

type Method struct {
    // Name是方法名。PkgPath是非導出字段的包路徑,對導出字段該字段為""。
    // 結合PkgPath和Name可以從方法集中指定一個方法。
    Name    string
    PkgPath string
    Type  Type  // 方法類型
    Func  Value // 方法的值
    Index int   // 用于Type.Method的索引
}

Method代表一個方法。

Value

Value為go值提供了反射接口。

不是所有go類型值的Value表示都能使用所有方法。請參見每個方法的文檔獲取使用限制。在調用有分類限定的方法時,應先使用Kind方法獲知該值的分類。調用該分類不支持的方法會導致運行時的panic。

Value類型的零值表示不持有某個值。零值的IsValid方法返回false,其Kind方法返回Invalid,而String方法返回<invalid Value>,所有其它方法都會panic。絕大多數函數和方法都永遠不返回Value零值。如果某個函數/方法返回了非法的Value,它的文檔必須顯式的說明具體情況。

如果某個go類型值可以安全的用于多線程并發操作,它的Value表示也可以安全的用于并發。

獲取Value對象的方法:

func ValueOf(i interface{}) Value

ValueOf返回一個初始化為i接口保管的具體值的Value,ValueOf(nil)返回Value零值。

例如:

str := "this is string"
val := reflect.ValueOf(str)

** Value中常用的方法:**

  • Kind()
func (v Value) Kind() Kind

Kind返回v持有的值的分類,如果v是Value零值,返回值為Invalid

  • func (Value) IsValid
func (v Value) IsValid() bool

IsValid返回v是否持有一個值。如果v是Value零值會返回假,此時v除了IsValid、String、Kind之外的方法都會導致panic。絕大多數函數和方法都永遠不返回Value零值。如果某個函數/方法返回了非法的Value,它的文檔必須顯式的說明具體情況。

  • func (Value) IsNil
func (v Value) IsNil() bool

IsNil報告v持有的值是否為nil。v持有的值的分類必須是通道、函數、接口、映射、指針、切片之一;否則IsNil函數會導致panic。注意IsNil并不總是等價于go語言中值與nil的常規比較。例如:如果v是通過使用某個值為nil的接口調用ValueOf函數創建的,v.IsNil()返回真,但是如果v是Value零值,會panic。

  • func (Value) Type
func (v Value) Type() Type

返回v持有的值的類型的Type表示。

  • func (Value) Elem
func (v Value) Elem() Value

Elem返回v持有的接口保管的值的Value封裝,或者v持有的指針指向的值的Value封裝。如果v的Kind不是Interface或Ptr會panic;如果v持有的值為nil,會返回Value零值。

  • func (Value) NumField
func (v Value) NumField() int

返回v持有的結構體類型值的字段數,如果v的Kind不是Struct會panic

  • func (Value) Field
func (v Value) Field(i int) Value

返回結構體的第i個字段(的Value封裝)。如果v的Kind不是Struct或i出界會panic

  • func (Value) FieldByIndex
func (v Value) FieldByIndex(index []int) Value

返回索引序列指定的嵌套字段的Value表示,等價于用索引中的值鏈式調用本方法,如v的Kind非Struct將會panic

  • func (Value) FieldByName
func (v Value) FieldByName(name string) Value

返回該類型名為name的字段(的Value封裝)(會查找匿名字段及其子字段),如果v的Kind不是Struct會panic;如果未找到會返回Value零值。

  • func (Value) NumMethod
func (v Value) NumMethod() int

返回v持有值的方法集的方法數目。

  • func (Value) Method
func (v Value) Method(i int) Value

返回v持有值類型的第i個方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝。返回值調用Call方法時不應包含接收者;返回值持有的函數總是使用v的持有者作為接收者(即第一個參數)。如果i出界,或者v的持有值是接口類型的零值(nil),會panic。

  • func (Value) MethodByName
func (v Value) MethodByName(name string) Value

返回v的名為name的方法的已綁定(到v的持有值的)狀態的函數形式的Value封裝。返回值調用Call方法時不應包含接收者;返回值持有的函數總是使用v的持有者作為接收者(即第一個參數)。如果未找到該方法,會返回一個Value零值。

  • func (Value) Call
func (v Value) Call(in []Value) []Value

Call方法使用輸入的參數in調用v持有的函數。例如,如果len(in) == 3,v.Call(in)代表調用v(in[0], in[1], in[2])(其中Value值表示其持有值)。如果v的Kind不是Func會panic。它返回函數所有輸出結果的Value封裝的切片。和go代碼一樣,每一個輸入實參的持有值都必須可以直接賦值給函數對應輸入參數的類型。如果v持有值是可變參數函數,Call方法會自行創建一個代表可變參數的切片,將對應可變參數的值都拷貝到里面。

  • func (Value) CallSlice
func (v Value) CallSlice(in []Value) []Value

CallSlice調用v持有的可變參數函數,會將切片類型的in[len(in)-1](的成員)分配給v的最后的可變參數。例如,如果len(in) == 3,v.Call(in)代表調用v(in[0], in[1], in[2])(其中Value值表示其持有值,可變參數函數的可變參數位置提供一個切片并跟三個點號代表"解切片")。如果v的Kind不是Func或者v的持有值不是可變參數函數,會panic。它返回函數所有輸出結果的Value封裝的切片。和go代碼一樣,每一個輸入實參的持有值都必須可以直接賦值給函數對應輸入參數的類型。

  • func (Value) Interface
func (v Value) Interface() (i interface{})

本方法返回v當前持有的值(表示為/保管在interface{}類型),等價于:

var i interface{} = (v's underlying value)

如果v是通過訪問非導出結構體字段獲取的,會導致panic。
另外還有很多類似這個函數的功能可以獲取value的值。

func (v Value) String() string
func (v Value) Bool() bool
func (v Value) Int() int64
func (v Value) Uint() uint64
func (v Value) Float() float64
func (v Value) Complex() complex128
func (v Value) Bytes() []byte
...
  • func (Value) CanSet
func (v Value) CanSet() bool

如果v持有的值可以被修改,CanSet就會返回真。只有一個Value持有值可以被尋址同時又不是來自非導出字段時,它才可以被修改。如果CanSet返回假,調用Set或任何限定類型的設置函數(如SetBool、SetInt64)都會panic。

  • 設置函數
func (v Value) SetBool(x bool)
func (v Value) SetInt(x int64)
func (v Value) SetUint(x uint64)
func (v Value) SetFloat(x float64)
func (v Value) SetComplex(x complex128)
func (v Value) SetBytes(x []byte)
func (v Value) SetString(x string)
...

例子

說了這么多,看下實例吧
對象:

package demo

import (
    "fmt"
)

type Address struct{
    City string
    Area string
}

type Student struct{
    Address
    Name string
    Age int
}

func (this Student) Say(){
    fmt.Println("hello, i am ", this.Name, "and i am ", this.Age)
}

func (this Student) Hello(word string){
    fmt.Println("hello", word, ". i am ", this.Name)
}

有關反射函數demo

package demo

import (
    "fmt"
    "reflect"
)

/*
  獲取對象的信息
*/
func StructInfo(o interface{}){
    //獲取對象的類型
    t := reflect.TypeOf(o)
    fmt.Println(t.Name(), "object type: ", t.Name())

    if k := t.Kind(); k != reflect.Struct{
        fmt.Println("the object is not a struct, but it is", t.Kind())
        return
    }

    //獲取對象的值
    v := reflect.ValueOf(o)
    fmt.Println(t.Name(), "object value: ", v)

    //獲取對象的字段
    fmt.Println(t.Name(), "fields: ")
    for i := 0; i < t.NumField(); i++{
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Printf("%6s:%v = %v \n", f.Name, f.Type, val)
        //通過遞歸調用獲取子類型的信息
        t1 := reflect.TypeOf(val)
        if k := t1.Kind(); k == reflect.Struct{
            StructInfo(val)
        }
    }
    //獲取對象的函數
    fmt.Println(t.Name(), "methods: ", t.NumMethod())
    for i := 0; i < t.NumMethod(); i++{
        m := t.Method(i)
        fmt.Printf("%10s:%v \n", m.Name, m.Type)
    }
}

/*
  匿名字段的反射
*/
func Annoy(o interface{}){
    t := reflect.TypeOf(o)
    for i := 0; i < t.NumField(); i++{
        f := t.Field(i)
        fmt.Printf("%10s:%#v \n", f.Name, f)
    }
}

/*
  通過反射設置字段
*/
func ReflectSet(o interface{}){
    v := reflect.ValueOf(o)
    if v.Kind() == reflect.Ptr && !v.Elem().CanSet(){
        fmt.Println("修改失敗")
        return
    }
    v = v.Elem()
    //獲取字段
    f := v.FieldByName("Name")
    if !f.IsValid(){
        fmt.Println("修改失敗")
        return
    }
    //設置值
    if f.Kind() == reflect.String{
        f.SetString("chairis")
    }
}

/* 
  通過反射調用函數
*/
func ReflectMethod(o interface{}){
    v := reflect.ValueOf(o)
    //無參函數調用
    m1:= v.MethodByName("Say")
    m1.Call([]reflect.Value{})

    //有參函數調用
    m2 := v.MethodByName("Hello")
    m2.Call([]reflect.Value{reflect.ValueOf("iris")})
}

上述代碼一共有以下這幾個函數:
StructInfo:是用來說明type, 和value的使用方式的按照以下方式調用:

package main

import (
    . "stu_demo/demo"
)

func main(){
    stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
    StructInfo(stu)
}

結果如下:


運行結果

Annoy : 用來展示匿名字段的函數。
照以下方式調用:

package main

import (
    . "stu_demo/demo"
)

func main(){
    stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
    Annoy(stu)
}

結果如下:


運行結果

ReflectSet : 是用來實現通過反射來修改字段值的函數。
按照以下調用:

package main

import (
    "fmt"
    . "stu_demo/demo"
)

func main(){
    stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
    fmt.Println("before: ", stu)
    ReflectSet(&stu)
    fmt.Println("after: ", stu)
}

結果如下:


運行結果

ReflectMethod : 是用來實現通過反射調用函數的方法。
按照以下調用:

package main

import (
    . "stu_demo/demo"
)

func main(){
    stu := Student{Address:Address{City:"Shanghai", Area:"Pudong"}, Name:"chain", Age:23}
    ReflectMethod(stu)
}

得以下結果:


運行結果

源碼

github 源碼地址

轉載請注明出處:Golang 學習筆記(11)—— 反射

目錄
上一節:Golang 學習筆記(10)—— mysql操作
下一節:Golang 學習筆記(12)—— ORM實現

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容