- 作者:BGbiao
- 链接:https://www.jianshu.com/p/42c19f88df6c
- 來源:简书
反射reflection
- 可以大大提高程序的灵活性,使得interface{}有更大的发挥余地
- 反射可以使用TypeOf和ValueOf函数从接口中获取目标对象信息
- 反射会将匿名字段作为独立字段(匿名字段的本质)
- 想要利用反射修改对象状态,前提是interface.data是settable,即pointer-interface
- 通过反射可以“动态”调用方法
常用的类型、函数和方法
反射的基本操作
通过反射来获取结构体字段的名称以及其他相关信息。
- 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)
- }
输出:
- 修改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