golang的reflection(转)
程序员文章站
2022-06-21 22:52:56
反射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