Golang的反射reflect
通过反射(Reflection),可以在程序运行时,获取对象的类型信息,包括确定对象的类、确定对象的类型的所有成员变量和方法、动态调用对象的方法。
接口类型变量转换为反射类型对象
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
gender string
}
func main() {
var num int = 100
t := reflect.TypeOf(num)
v := reflect.ValueOf(num)
fmt.Println(t, v) //int 100
p := Person{"John", 30, "male"}
t = reflect.TypeOf(p)
v = reflect.ValueOf(p)
fmt.Println(t, v) //main.Person {John 30 male}
}
反射类型对象转换为接口类型变量
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
gender string
}
func main() {
//接口类型对象转为反射类型对象
p := Person{"John", 30, "male"}
t = reflect.TypeOf(p)
v = reflect.ValueOf(p)
fmt.Println(t, v) //main.Person {John 30 male}
//反射类型对象转换为接口类型对象
p = v.Interface().(Person)
fmt.Println(p) //{John 30 male}
}
转换前后,p是一样的(内存地址是一样的)
通过反射类型对象修改接口类型变量的值
如果要修改“反射类型对象”,其值必须是可写的。
反射对象包含了接口变量中存储的值以及类型,如果反射对象中包含的是原始值,那么可以通过反射对象修改原始值;如果反射对象中包含的值不是原始对象,比如反射对象包含的是副本值或者指向原始值的地址,那么反射对象是不可修改的。
操作普通类型:
package main
import (
"fmt"
"reflect"
)
func main() {
var pi float64 = 3.1425926
/*
//出错原因:将pi传递给ValueOf,是传值,ValueOf接收到的是pi的副本
// 所以不能修改pi的值
v := reflect.ValueOf(pi)
v.SetFloat(3.14)
*/
/*
//出错原因:虽然传递的是pi的地址,但是进行修改的时候,修改的却是pi的地址,不是pi的地址执行的内容
v := reflect.ValueOf(&pi)
v.SetFloat(3.14)
*/
//正确 Elem()相当于解引用
v := reflect.ValueOf(&pi).Elem()
v.SetFloat(3.14)
fmt.Println(v) //3.14
fmt.Println(pi) //3.14
}
遍历、修改结构体属性信息
//package main
//import (
// "fmt"
// "reflect"
// "strconv"
//)
//type Person struct {
// Name string
// Age int
//}
////注意这里是传的指针类型
////注意方法名开头是小写
//func (p *Person) setName(name string) {
// p.Name = name
//}
////注意这里是传的指针类型
////注意方法名首字母大写
//func (p *Person) SetAge(age int) {
// p.Age = age
//}
////传值形式
////注意方法名首字母小写
//func (p Person) saySelf() string {
// return "My Name:" + p.Name + ", My Age:" + strconv.Itoa(p.Age)
//}
////传值形式
////注意方法名首字母大写
//func (p Person) ToString() string {
// return "Name:" + p.Name + ", Age:" + strconv.Itoa(p.Age)
//}
//func main() {
// //注意这里的p是Person对象的值
// p := &Person{"John", 99}
// //注意
// v := reflect.ValueOf(&p).Elem()
// //方法数量
// count := v.NumMethod()
// for i := 0; i < count; i++ {
// fmt.Println(v.Type().Method(i).Name, v.Method(i).Type())
// }
//}
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int //注意首字母大写
gender string //注意首字母小写
}
func main() {
p := Person{"John", 99, "male"}
//传递地址,然后解引用,此时v就是p对象本身
v := reflect.ValueOf(&p).Elem()
//p(v)的属性数量
count := v.NumField()
for i := 0; i < count; i++ {
fmt.Println(v.Type().Field(i).Name) //属性名
fmt.Println(v.Field(i).Type()) //属性类型
fmt.Println(v.Field(i)) //属性值
}
// 打印结果:
// Name string John
// Age int 99
// gender string male
//修改第一个属性的值
v.Field(0).SetString("Jane")
//修改Age属性的值
v.FieldByName("Age").SetInt(88)
fmt.Println(v) //{Jane 88 male}
fmt.Println(p) //{Jane 88 male}
}
注意:不管结构体的首字母是大写还是小写,通过这种方法都是可以遍历出出来。
遍历、调用结构体中的方法
package main
import (
"fmt"
"reflect"
"strconv"
)
type Person struct {
Name string
Age int
}
//注意这里是传的指针类型
//注意方法名开头是小写
func (p *Person) setName(name string) {
p.Name = name
}
//注意这里是传的指针类型
//注意方法名首字母大写
func (p *Person) SetAge(age int) {
p.Age = age
}
//传值形式
//注意方法名首字母小写
func (p Person) saySelf() string {
return "My Name:" + p.Name + ", My Age:" + strconv.Itoa(p.Age)
}
//传值形式
//注意方法名首字母大写
func (p Person) ToString() string {
return "Name:" + p.Name + ", Age:" + strconv.Itoa(p.Age)
}
func main() {
//注意这里的p是Person对象的值
p := Person{"John", 99}
//注意
v := reflect.ValueOf(&p).Elem()
//方法数量
count := v.NumMethod()
for i := 0; i < count; i++ {
fmt.Println(v.Type().Method(i).Name, v.Method(i).Type())
}
}
上面程序运行结果是:ToString func() string
没错,只有这一个方法被输出了,这里有两个注意点:
1、方法首字母的大小写
2、接受参数的类型
3、在主函数中结构体对象是对象,还是对象指针
对于第1个问题:只有首字母是大写的方法才是可导出的,对于成员属性也是一样的。
对于第2个问题:这个问题要和第3个问题一起说,
如果传递给ValueOf一个对象的地址,就可以将定义在结构体上的、接收者是一个传值的方法,并且首字母大写的方法遍历出来;
如果传递给ValueOf一个对象的地址的地址,即取两次地址,就可以找到定义在结构体上的、接受者是一个对象指针的或者是一个传值的方法、并且首字母大写的方法。
看下面的代码:
package main
import (
"fmt"
"reflect"
"strconv"
)
type Person struct {
Name string
Age int
}
//注意这里是传的指针类型
//注意方法名开头是小写
func (p *Person) setName(name string) {
p.Name = name
}
//注意这里是传的指针类型
//注意方法名首字母大写
func (p *Person) SetAge(age int) {
p.Age = age
}
//传值形式
//注意方法名首字母小写
func (p Person) saySelf() string {
return "My Name:" + p.Name + ", My Age:" + strconv.Itoa(p.Age)
}
//传值形式
//注意方法名首字母大写
func (p Person) ToString() string {
return "Name:" + p.Name + ", Age:" + strconv.Itoa(p.Age)
}
func main() {
//注意这里的p是Person对象的值
p := &Person{"John", 99}
//注意
v := reflect.ValueOf(&p).Elem()
//方法数量
count := v.NumMethod()
for i := 0; i < count; i++ {
fmt.Println(v.Type().Method(i).Name, v.Method(i).Type())
}
}
这段代码和上面一段代码就只有第40行,多了一个取地址。
运行结果:
SetAge func(int)
ToString func() string
没错,打印出了两个方法。
利用反射调用结构体的方法
package main
import (
"fmt"
"reflect"
"strconv"
)
type Person struct {
Name string
Age int
}
//注意这里是传的指针类型
//注意方法名开头是小写
func (p *Person) setName(name string) {
p.Name = name
}
//注意这里是传的指针类型
//注意方法名首字母大写
func (p *Person) SetAge(age int) {
p.Age = age
}
//传值形式
//注意方法名首字母小写
func (p Person) saySelf() string {
return "My Name:" + p.Name + ", My Age:" + strconv.Itoa(p.Age)
}
//传值形式
//注意方法名首字母大写
func (p Person) ToString() string {
return "Name:" + p.Name + ", Age:" + strconv.Itoa(p.Age)
}
func main() {
//注意这里的p是Person对象的值
p := &Person{"John", 99}
//注意
v := reflect.ValueOf(&p).Elem()
//方法数量
count := v.NumMethod()
for i := 0; i < count; i++ {
fmt.Println(v.Type().Method(i).Name, v.Method(i).Type())
}
//输出
//SetAge func(int)
//ToString func() string
//调用结构体的方法
params := make([]reflect.Value, 1)
params[0] = reflect.ValueOf(50)
v.Method(0).Call(params) //调用SetAge方法
fmt.Println(v) //&{John 50}
fmt.Println(p) //&{John 50}
res := v.MethodByName("ToString").Call(nil)
fmt.Println(res[0]) //Name:John, Age:50
}
如需转载,请注明文章出处,谢谢!!!
浙公网安备 33010602011771号