reflect概述
反射是一种检查存储在 接口变量中的<值,类型>对
的机制,借助go
反射包提供的reflect.TypeOf
和reflect.ValueOf
可以方便的访问到一个接口值的reflect.Type
和reflect.Value
部分,从而可进一步得到 这个接口的结构类型和对其进行值的修改操作
。
反射的使用
- 获取接口对象的字段,类型和方法信息
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()得到对应的value
for i := 0; i < t.NumField(); i++ { //NumField取出这个接口所有的字段数量
f := t.Field(i) //取得结构体的第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()循环获取接口对象所有方法的名称和类型。
- 反射接口对象中的匿名或嵌入字段信息
先再添加一个Manager结构,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结构体中的Id字段。
2.通过FieldByName("Id")也可以取得User结构体中Id字段。
- 通过反射修改对象
上面通过reflect.TypeOf
和reflect.ValueOf
已经可以得到接口对象的类型,字段和方法等属性了,怎么通过反射来修改对象的字段值?
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
变量的地址值类型之后先调用Elem()
返回地址指针指向的值的Value封装。然后通过Set方法进行修改赋值。
通过反射可以很容易的修改变量的值,怎么来修改结构体中的字段值?
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
}
//设置字段
if f := v.FieldByName("Name"); f.Kind() == reflect.String {
f.SetString("BY")
}
}
func main() {
inputs := Person{"sbd", 12}
SetInfo(&inputs)
fmt.Printf("%+v", inputs)
}
要成功修改结构体中的某个字段,主要进行以下操作:
- 首先要反射出这个字段的地址值类型;
- 判断反射返回类型是否为reflect.Ptr指针类型(通过指针才能操作对象地址中的值)同时还要判断这个元素是否可以修改;
- 通过FieldByName的返回值判断字段是否存在
- 通过Kind()和Set来修改字段的值
- 通过反射“动态”调用方法
现在已经可以通过反射获取并修改接口对象的字段,类型等信息了,那怎么通过反射“动态”调用接口对象等方法?
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)} //初始化传入等参数,传入等类型只能是[]reflect.Value类型
res := mv.Call(args)
fmt.Println(res[0], res[1])
}
func main() {
inputs := Person{"sbd", 12}
SetInfo(inputs)
}
通过MethodByName
先获取对象的Hello
方法,然后准备要传入的参数,这里传入的参数必须是[]refelct.Value
类型,传入的参数值必须强制转换为反射值类型refelct.Value
。
最后通过调用Call
方法就可以实现通过反射”动态”调用对象的方法。
http://researchlab.github.io/2016/02/17/go-reflect-summarize/