反射

反射可以在程序运行时检查任何变量的类型和值,同时也可以修改(或设置)变量的值。

获取值和类型

其中最常用的两个方法原型如下:

  1. func TypeOf(i interface{}) Type // 返回 i 的类型
  2. func ValueOf(i interface{}) Value // 返回 i 的值

其中 Type 和 Value 分别是两个结构体类型。

  1. var x float64 = 3.1415926
  2. xt := reflect.TypeOf(x)
  3. xv := reflect.ValueOf(x)
  4. fmt.Println(xt, xv) // float64 3.1415926

不要被输出所误导,认为 xt 和 xt 就是 float64 和 3.1415926,那是因为输出时作了自动处理。
事实上 xt 的类型是 Type 结构体,它可以调用属于该类型的任何方法,如 xt.Kind() 才是真正返回变量 x 的类型的

  1. fmt.Println(xt.Kind() == reflect.Float64) // true

同理 xv 的类型是 Value 结构体,它可以调用属于该类型的任何方法。
至于 Type 和 Value 都拥有哪些方法,作用是什么,这里就不深入了(因为我也没看= =)。

设置或修改值

如何通过反射把变量 x 的值改为 3.1415926535 呢?
Value 类型的方法可以做到,但需要谨慎使用。因为值是否可设置是 Value 的一个属性,不是所有的类型的反射值 Value 都有这个属性。

  1. 首先通过 Canset() 方法判断是否可以设置值。

    1. fmt.Println(xv.CanSet()) // false
  2. 当前是不可设置的,因为 xv := reflect.ValueOf(x) 是通过拷贝 x 创建了 xv,xv 的改变并不能影响 x。解决办法是传 x 的地址,然后通过 Elem() 方法变成可设置。

    1. xv := reflect.ValueOf(&x)
    2. fmt.Println(xv.CanSet()) // false
    3. xv = xv.Elem() // change to setable
    4. fmt.Println(xv.CanSet()) // true
  3. 最后通过 SetFloat() 方法设置新值(是什么类型就用什么 set 方法)。

    1. xv.SetFloat(3.1415926535)
    2. fmt.Println(xv.Float()) // 3.1415926535

反射结构体变量

对结构体变量进行反射,除了可以获得它的类型,还可以遍历输出它的字段和调用它的方法。

遍历字段和调用方法

  1. type Box struct {
  2. Name string
  3. Weight int
  4. }
  5. func (b Box) ShowInfo() {
  6. fmt.Printf("%+v %+v", b.Name, b.Weight)
  7. }
  8. func (b Box) SetName(name string) {
  9. b.Name = name
  10. }
  11. func main() {
  12. var b interface{} = Box{Name: "magic", Weight: 66}
  13. bv := reflect.ValueOf(b)
  14. // 输出字段
  15. for i := 0; i < bv.NumField(); i++ {
  16. fmt.Printf("Filed %d: %v\n", i, bv.Field(i))
  17. }
  18. // 输出方法个数
  19. fmt.Println(bv.NumMethod()) // 2
  20. bv.MethodByName("ShowInfo").Call(nil) // magic 66
  21. //bv.MethodByName("SetName").Call() // 不知道如何传参
  22. }

Call(in []Value) 不知道调用需要参数的方法。

修改字段值

要修改字段的值,字段必须是导出的(即首字母大写,包外访问)。

  1. b := Box{Name: "magic", Weight: 66}
  2. //var b interface{} = Box{Name: "magic", Weight: 66} // panic
  3. bv := reflect.ValueOf(&b).Elem()
  4. for i := 0; i < bv.NumField(); i++ {
  5. fmt.Printf("Filed %d: %v\n", i, bv.Field(i))
  6. }
  7. // 修改
  8. bv.Field(0).SetString("lie")
  9. bv.Field(1).SetInt(88)
  10. for i := 0; i < bv.NumField(); i++ {
  11. fmt.Printf("Filed %d: %v\n", i, bv.Field(i))
  12. }
  13. 输出如下:
  14. Filed 0: magic
  15. Filed 1: 66
  16. Filed 0: lie
  17. Filed 1: 88

注意到此时 b 的类型不能是 interface{},否则报错:panic: reflect: call of reflect.Value.NumField on interface Value。
也许可以猜测字段的顺序即为定义的顺序?