反射reflection 可以大大提高程式的靈活性,使得interface{}有更大的發揮餘地 反射可以使用TypeOf和ValueOf函數從介面中獲取目標對象信息 反射會將匿名欄位作為獨立欄位(匿名欄位的本質) 想要利用反射修改對象狀態,前提是interface.data是settable,即po ...
作者:BGbiao 鏈接:https://www.jianshu.com/p/42c19f88df6c 來源:簡書
反射reflection
- 可以大大提高程式的靈活性,使得interface{}有更大的發揮餘地
- 反射可以使用TypeOf和ValueOf函數從介面中獲取目標對象信息
- 反射會將匿名欄位作為獨立欄位(匿名欄位的本質)
- 想要利用反射修改對象狀態,前提是interface.data是settable,即pointer-interface
- 通過反射可以“動態”調用方法
常用的類型、函數和方法
//返回動態類型i的類型,如果i是一個空結構體類型,TypeOf將返回nil func TypeOf(i interface{}) Type //Type 介面類型 type Type interface { Align() int FieldAlign() int //指定結構體中方法的下標,返回某個方法的對象,需要註意的是返回的Method是一個獨立的結構體 Method(int) Method /* type Method struct { Name string PkgPath string Type Type Func Value Index int } */ MethodByName(string) (Method, bool) //返回該結構體類型的方法下標 NumMethod() int //返回類型的名稱,即動態類型i的名稱 Name() string PkgPath() string Size() uintptr String() string Kind() Kind Implements(u Type) bool AssignableTo(u Type) bool ConvertibleTo(u Type) bool Comparable() bool Bits() int ChanDir() ChanDir IsVariadic() bool Elem() Type //返回結構體類型第i個欄位 Field(i int) StructField //StructField結構體 //type StructField struct { // Name string // PkgPath string // Type Type // Tag StructTag // Offset uintptr // Index []int // Anonymous bool //根據結構體欄位索引獲取嵌入欄位的結構體信息 FieldByIndex(index []int) StructField FieldByName(name string) (StructField, bool) FieldByNameFunc(match func(string) bool) (StructField, bool) In(i int) Type Key() Type Len() int //返回動態類型i(結構體欄位)的欄位總數 NumField() int NumIn() int NumOut() int Out(i int) Type } //返回介面i的一個初始化的新值.ValueOf(nil)返回一個零值 func ValueOf(i interface{}) Value // Value結構體 type Value struct { } // Value結構體的一些方法 // 返回結構體v中的第i個欄位。如果v的類型不是結構體或者i超出了結構體的範圍,則會出現panic func (v Value) Field(i int) Value //以介面類型返回v的當前值 func (v Value) Interface() (i interface{}) //等價於. var i interface{} = (v's underlying value) //通過反射方式修改結構體對象的一些方法 //返回介面v包含或者指針v包含的值 func (v Value) Elem() Value //判斷該介面v是否可以被set修改 func (v Value) CanSet() bool //使用另外一個反射介面去修改反射值 func (v Value) Set(x Value) //其他不同類型的Set func (v Value) SetBool(x bool) func (v Value) SetBytes(x []byte) func (v Value) SetFloat(x float64) func (v Value) SetInt(x int64) //設置結構體對象v的長度為n func (v Value) SetLen(n int) func (v Value) SetString(x string) //一些輔助方法 //返回反射結構體的Value的類型.如果v為零值,IsValid將返回false func (v Value) Kind() Kind //判斷value是否為有效值,通常用在判斷某個欄位是否在反射體的Value中 func (v Value) IsValid() bool //Kind常量 type Kind uint 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 )
反射的基本操作
通過反射來獲取結構體欄位的名稱以及其他相關信息。
package main import ( "fmt" "reflect" ) //定義結構體 type User struct { Id int Name string Age int } //定義結構體方法 func (u User) Hello() { fmt.Println("Hello xuxuebiao") } func main() { u := User{1, "bgops", 25} Info(u) u.Hello() } //定義一個反射函數,參數為任意類型 func Info(o interface{}) { //使用反射類型獲取o的Type,一個包含多個方法的interface t := reflect.TypeOf(o) //列印類型o的名稱 fmt.Println("type:", t.Name()) //使用反射類型獲取o的Value,一個空的結構體 v := reflect.ValueOf(o) fmt.Println("Fields:") //t.NumField()列印結構體o的欄位個數(Id,Name,Age共三個) for i := 0; i < t.NumField(); i++ { //根據結構體的下標i來獲取結構體某個欄位,並返回一個新的結構體 /** type StructField struct { Name string PkgPath string Type Type Tag StructTag Offset uintptr Index []int Anonymous bool } **/ f := t.Field(i) //使用結構體方法v.Field(i)根據下標i獲取欄位Value(Id,Name,Age) //在根據Value的Interface()方法獲取當前的value的值(interface類型) val := v.Field(i).Interface() fmt.Printf("%6s:%v = %v\n", f.Name, f.Type, val) } //使用t.NumMethod()獲取所有結構體類型的方法個數(只有Hello()一個方法) //介面Type的方法NumMethod() int for i := 0; i < t.NumMethod(); i++ { //使用t.Method(i)指定方法下標獲取方法對象。返回一個Method結構體 //Method(int) Method m := t.Method(i) //列印Method結構體的相關屬性 /* type Method struct { Name string PkgPath string Type Type Func Value Index int } */ fmt.Printf("%6s:%v\n", m.Name, m.Type) } }
輸出
type: User Fields: Id:int = 1 Name:string = bgops Age:int = 25 Hello:func(main.User) Hello xuxuebiao
註意:我們上面的示例是使用值類型進行進行反射構造的。如果是指針類型的話,我們需要使用reflect.Struct欄位進行判斷介面類型的Kind()方法
if k := t.Kind();k != reflect.Struct { fmt.Println("非值類型的反射") return }
匿名欄位的反射以及嵌入欄位
註意:反射會將匿名欄位當做獨立的欄位去處理,需要通過類型索引方式使用FieldByIndex方法去逐個判斷
//根據指定索引返回對應的嵌套欄位 FieldByIndex(index []int) StructField type StructField struct { Name string PkgPath string Type Type Tag StructTag Offset uintptr Index []int Anonymous bool //是否為匿名欄位 }
package main import ( "fmt" "reflect" ) type User struct { Id int Name string Age int } type Manager struct { User title string } func main() { //註意匿名欄位的初始化操作 m := Manager{User: User{1, "biaoge", 24}, title: "hello biao"} t := reflect.TypeOf(m) fmt.Printf("%#v\n", t.FieldByIndex([]int{0})) fmt.Printf("%#v\n", t.FieldByIndex([]int{1})) fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 0})) fmt.Printf("%#v\n", t.FieldByIndex([]int{0, 1})) }
輸出:
reflect.StructField{Name:"User", PkgPath:"", Type:(*reflect.rtype)(0x4ac660), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true} reflect.StructField{Name:"title", PkgPath:"main", Type:(*reflect.rtype)(0x49d820), Tag:"", Offset:0x20, Index:[]int{1}, Anonymous:false} reflect.StructField{Name:"Id", PkgPath:"", Type:(*reflect.rtype)(0x49d1a0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false} reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x49d820), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}
通過反射修改目標對象
通過反射的方式去修改對象的某個值。需要註意的亮點是,首先,需要找到對象相關的名稱,其次需要找到合適的方法去修改相應的值。
package main import ( "fmt" "reflect" ) func main() { x := 123 v := reflect.ValueOf(&x) v.Elem().SetInt(5256) fmt.Println(x) }
輸出:
5256
修改I的時候需要找到對象欄位的名稱;並且判斷類型,使用相對正確的類型修改值
v.FieldByName("Name");f.Kind() == reflect.String { f.SetString("test string") }
判斷是否找到正確的欄位名稱:
f := v.FieldByName("Name1") //判斷反射對象Value中是否找到Name1欄位 if !f.IsValid() { fmt.Println("bad field") return }
package main import ( "fmt" "reflect" ) type User struct { Id int Name string Age int } //使用反射方式對結構體對象的修改有兩個條件 //1.通過指針 //2.必須是可set的方法 func main() { num := 123 numv := reflect.ValueOf(&num) //通過Value的Elem()和SetX()方法可直接對相關的對象進行修改 numv.Elem().SetInt(666) fmt.Println(num) u := User{1, "biao", 24} uu := reflect.ValueOf(&u) //Set()後面的必須是值類型 //func (v Value) Set(x Value) test := User{2, "bgops", 2} testv := reflect.ValueOf(test) uu.Elem().Set(testv) fmt.Println("Change the test to u with Set(x Value)", uu) //此時的U已經被上面那個uu通過指針的方式修改了 Set(&u) fmt.Println(u) } func Set(o interface{}) { v := reflect.ValueOf(o) //判斷反射體值v是否是Ptr類型並且不能進行Set操作 if v.Kind() == reflect.Ptr && ! v.Elem().CanSet() { fmt.Println("xxx") return //初始化對象修改後的返回值(可接受v或v的指針) } else { v = v.Elem() } //按照結構體對象的名稱進行查找filed,並判斷類型是否為string,然後進行Set if f := v.FieldByName("Name"); f.Kind() == reflect.String { f.SetString("BYBY") } }
輸出:
666 Change the test to u with Set(x Value) &{2 bgops 2} {2 BYBY 2}
通過反射進行動態方法的調用
使用反射的相關知識進行方法的動態調用
package main import ( "fmt" "reflect" ) type User struct { Id int Name string Age int } func (u User) Hello(name string, id int) { fmt.Printf("Hello %s,my name is %s and my id is %d\n", name, u.Name, id) } func main() { u := User{1, "biaoge", 24} fmt.Println("方法調用:") u.Hello("xuxuebiao", 121) //獲取結構體類型u的Value v := reflect.ValueOf(u) //根據方法名稱獲取Value中的方法對象 mv := v.MethodByName("Hello") //構造一個[]Value類型的變數,使用Value的Call(in []Value)方法進行動態調用method //這裡其實相當於有一個Value類型的Slice,僅一個欄位 args := []reflect.Value{reflect.ValueOf("xuxuebiao"), reflect.ValueOf(5256)} fmt.Println("通過反射動態調用方法:") //使用Value的Call(in []Value)方法進行方法的動態調用 //func (v Value) Call(in []Value) []Value //需要註意的是當v的類型不是Func的化,將會panic;同時每個輸入的參數args都必須對應到Hello()方法中的每一個形參上 mv.Call(args) }
方法調用: Hello xuxuebiao,my name is biaoge and my id is 121 通過反射動態調用方法: Hello xuxuebiao,my name is biaoge and my id is 5256