反射
反射可以在程序运行时检查任何变量的类型和值,同时也可以修改(或设置)变量的值。
获取值和类型
其中最常用的两个方法原型如下:
func TypeOf(i interface{}) Type // 返回 i 的类型
func ValueOf(i interface{}) Value // 返回 i 的值
其中 Type 和 Value 分别是两个结构体类型。
var x float64 = 3.1415926
xt := reflect.TypeOf(x)
xv := reflect.ValueOf(x)
fmt.Println(xt, xv) // float64 3.1415926
不要被输出所误导,认为 xt 和 xt 就是 float64 和 3.1415926,那是因为输出时作了自动处理。
事实上 xt 的类型是 Type 结构体,它可以调用属于该类型的任何方法,如 xt.Kind() 才是真正返回变量 x 的类型的
fmt.Println(xt.Kind() == reflect.Float64) // true
同理 xv 的类型是 Value 结构体,它可以调用属于该类型的任何方法。
至于 Type 和 Value 都拥有哪些方法,作用是什么,这里就不深入了(因为我也没看= =)。
设置或修改值
如何通过反射把变量 x 的值改为 3.1415926535 呢?
Value 类型的方法可以做到,但需要谨慎使用。因为值是否可设置是 Value 的一个属性,不是所有的类型的反射值 Value 都有这个属性。
首先通过 Canset() 方法判断是否可以设置值。
fmt.Println(xv.CanSet()) // false
当前是不可设置的,因为 xv := reflect.ValueOf(x) 是通过拷贝 x 创建了 xv,xv 的改变并不能影响 x。解决办法是传 x 的地址,然后通过 Elem() 方法变成可设置。
xv := reflect.ValueOf(&x)
fmt.Println(xv.CanSet()) // false
xv = xv.Elem() // change to setable
fmt.Println(xv.CanSet()) // true
最后通过 SetFloat() 方法设置新值(是什么类型就用什么 set 方法)。
xv.SetFloat(3.1415926535)
fmt.Println(xv.Float()) // 3.1415926535
反射结构体变量
对结构体变量进行反射,除了可以获得它的类型,还可以遍历输出它的字段和调用它的方法。
遍历字段和调用方法
type Box struct {
Name string
Weight int
}
func (b Box) ShowInfo() {
fmt.Printf("%+v %+v", b.Name, b.Weight)
}
func (b Box) SetName(name string) {
b.Name = name
}
func main() {
var b interface{} = Box{Name: "magic", Weight: 66}
bv := reflect.ValueOf(b)
// 输出字段
for i := 0; i < bv.NumField(); i++ {
fmt.Printf("Filed %d: %v\n", i, bv.Field(i))
}
// 输出方法个数
fmt.Println(bv.NumMethod()) // 2
bv.MethodByName("ShowInfo").Call(nil) // magic 66
//bv.MethodByName("SetName").Call() // 不知道如何传参
}
Call(in []Value)
不知道调用需要参数的方法。
修改字段值
要修改字段的值,字段必须是导出的(即首字母大写,包外访问)。
b := Box{Name: "magic", Weight: 66}
//var b interface{} = Box{Name: "magic", Weight: 66} // panic
bv := reflect.ValueOf(&b).Elem()
for i := 0; i < bv.NumField(); i++ {
fmt.Printf("Filed %d: %v\n", i, bv.Field(i))
}
// 修改
bv.Field(0).SetString("lie")
bv.Field(1).SetInt(88)
for i := 0; i < bv.NumField(); i++ {
fmt.Printf("Filed %d: %v\n", i, bv.Field(i))
}
输出如下:
Filed 0: magic
Filed 1: 66
Filed 0: lie
Filed 1: 88
注意到此时 b 的类型不能是 interface{},否则报错:panic: reflect: call of reflect.Value.NumField on interface Value。
也许可以猜测字段的顺序即为定义的顺序?