reflect概述
反射是一種檢查存儲在 接口變量中的<值,類型>對
的機制窄绒,借助go
反射包提供的reflect.TypeOf
和reflect.ValueOf
可以方便的訪問到一個接口值的reflect.Type
和reflect.Value
部分港准,從而可進一步得到 這個接口的結(jié)構(gòu)類型和對其進行值的修改操作
。
反射的使用
- 獲取接口對象的字段,類型和方法信息
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func (p Person)SetName(name string){
}
func (p Person)SetAge(age int){
p.Age = age
}
func Info(o interface{}) {
t := reflect.TypeOf(o) //獲取接口的類型
fmt.Println("Type:", t.Name()) //t.Name() 獲取接口的名稱
if t.Kind() != reflect.Struct { //通過Kind()來判斷反射出的類型是否為需要的類型
fmt.Println("err: type invalid!")
return
}
v := reflect.ValueOf(o) //獲取接口的值類型
fmt.Println("Fields:")
// 1. 先獲取interface的reflect.Type檬果,然后通過NumField進行遍歷
// 2. 再通過reflect.Type的Field獲取其Field
// 3. 最后通過Field的Interface()得到對應(yīng)的value
for i := 0; i < t.NumField(); i++ { //NumField取出這個接口所有的字段數(shù)量
f := t.Field(i) //取得結(jié)構(gòu)體的第i個字段
val := v.Field(i).Interface() //取得字段的值
fmt.Printf("%6s: %v = %v\n", f.Name, f.Type, val) //第i個字段的名稱,類型,值
}
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("%6s: %v\n", m.Name, m.Type) //獲取方法的名稱和類型
}
}
func main() {
inputs := Person{"sbd", 12}
inputs.SetAge(11)
Info(inputs)
}
result:
Type: Person
Fields:
Name: string = sbd
Age: int = 12
SetAge: func(main.Person, int)
SetName: func(main.Person, string)
interface
的reflect.Type
是User
,不是*User
, 因此
func (p *Person)SetName(name string){
}
不會被反射出來
- 獲取接口對象的類型名稱泽篮,通過refelct.TypeOf()獲取接口對象的類型,并通過Name()方法獲取接口的名稱普气。
- 獲取對象中所有字段的名稱,類型和值,通過reflect.ValueOf()獲取接口對象的值類型取得字段的名稱和類型,然后通過v.Field(i).Interface()取得第i個字段的值谜疤。
- 還可以通過NumMethod()循環(huán)獲取接口對象所有方法的名稱和類型。
- 反射接口對象中的匿名或嵌入字段信息
先再添加一個Manager結(jié)構(gòu),User作為它的匿名字段
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
type Manage struct{
Person
Title string
}
func Info(m interface{}) {
t := reflect.TypeOf(m)
fmt.Println("Type:", t.Name())
//t := refelct.TypeOf(m)將Manager的字段類型取出來,在反射中對象字段是通過索引取到的棋电,所以可通過t.Field(0)
fmt.Printf("%#v\n", t.Field(0))
fmt.Printf("%+v\n", t.FieldByIndex([]int{0,1}))
}
func main() {
inputs := Manage{Person{"xudong", 18}, "yuwen"}
Info(inputs)
}
1.給FieldByIndex()傳入一個int型的slice索引,如FieldByIndex([]int{0,0})即取得User結(jié)構(gòu)體中的Id字段茎截。
2.通過FieldByName("Id")也可以取得User結(jié)構(gòu)體中Id字段。
- 通過反射修改對象
上面通過reflect.TypeOf
和reflect.ValueOf
已經(jīng)可以得到接口對象的類型,字段和方法等屬性了赶盔,怎么通過反射來修改對象的字段值企锌?
func main() {
x := 100
v := reflect.ValueOf(&x)
v.Elem().SetInt(200)
fmt.Println(x)
}
200
要修改變量x
的值,首先就要通過reflect.ValueOf
來獲取x
的值類型,refelct.ValueO
f返回的值類型是變量x
一份值拷貝,要修改變量x
就要傳遞x
的地址,從而返回x
的地址對象,才可以進行對x變量值對修改操作于未。在得到x
變量的地址值類型之后先調(diào)用Elem()
返回地址指針指向的值的Value封裝撕攒。然后通過Set方法進行修改賦值陡鹃。
通過反射可以很容易的修改變量的值,怎么來修改結(jié)構(gòu)體中的字段值抖坪?
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func SetInfo(o interface{}) {
v := reflect.ValueOf(o)
if v.Kind() == reflect.Ptr && !v.Elem().CanSet() { //判斷是否為指針類型 元素是否可以修改
fmt.Println("cannot set")
return
} else {
v = v.Elem() //實際取得的對象
}
//判斷字段是否存在
f := v.FieldByName("Name")
if !f.IsValid() {
fmt.Println("wuxiao")
return
}
//設(shè)置字段
if f := v.FieldByName("Name"); f.Kind() == reflect.String {
f.SetString("BY")
}
}
func main() {
inputs := Person{"sbd", 12}
SetInfo(&inputs)
fmt.Printf("%+v", inputs)
}
要成功修改結(jié)構(gòu)體中的某個字段,主要進行以下操作:
- 首先要反射出這個字段的地址值類型;
- 判斷反射返回類型是否為reflect.Ptr指針類型(通過指針才能操作對象地址中的值)同時還要判斷這個元素是否可以修改;
- 通過FieldByName的返回值判斷字段是否存在
- 通過Kind()和Set來修改字段的值
- 通過反射“動態(tài)”調(diào)用方法
現(xiàn)在已經(jīng)可以通過反射獲取并修改接口對象的字段萍鲸,類型等信息了,那怎么通過反射“動態(tài)”調(diào)用接口對象等方法擦俐?
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
}
func (p Person) GetInfo(v Person) (name string, age int) {
return p.Name, p.Age
}
func SetInfo(o interface{}) {
v := reflect.ValueOf(o)
if v.Kind() != reflect.Struct {
fmt.Println("not struct")
return
}
//判斷字段是否存在
mv := v.MethodByName("GetInfo")
if !mv.IsValid() {
fmt.Println("wuxiao")
return
}
args := []reflect.Value{reflect.ValueOf(o)} //初始化傳入等參數(shù)脊阴,傳入等類型只能是[]reflect.Value類型
res := mv.Call(args)
fmt.Println(res[0], res[1])
}
func main() {
inputs := Person{"sbd", 12}
SetInfo(inputs)
}
通過MethodByName
先獲取對象的Hello
方法,然后準備要傳入的參數(shù),這里傳入的參數(shù)必須是[]refelct.Value
類型,傳入的參數(shù)值必須強制轉(zhuǎn)換為反射值類型refelct.Value
。
最后通過調(diào)用Call
方法就可以實現(xiàn)通過反射”動態(tài)”調(diào)用對象的方法蚯瞧。
http://researchlab.github.io/2016/02/17/go-reflect-summarize/