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 }
如需转载,请注明文章出处,谢谢!!!