11.10.1 方法和类型的反射

在 10.4 节我们看到可以通过反射来分析一个结构体。本节我们进一步探讨强大的反射功能。反射是用程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。反射可以在运行时检查类型和变量,例如它的大小、方法和 动态的调用这些方法。这对于没有源代码的包尤其有用。这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。

变量的最基本信息就是类型和值:反射包的 Type 用来表示一个 Go 类型,反射包的 Value 为 Go 值提供了反射接口。

两个简单的函数,reflect.TypeOfreflect.ValueOf,返回被检查对象的类型和值。例如,x 被定义为:var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64reflect.ValueOf(x) 返回 <float64 Value>

实际上,反射是通过检查一个接口的值,变量首先被转换成空接口。这从下面两个函数签名能够很明显的看出来:

  1. func TypeOf(i interface{}) Type
  2. func ValueOf(i interface{}) Value

接口的值包含一个 type 和 value。

反射可以从接口值反射到对象,也可以从对象反射回接口值。

reflect.Type 和 reflect.Value 都有许多方法用于检查和操作它们。一个重要的例子是 Value 有一个 Type 方法返回 reflect.Value 的 Type。另一个是 Type 和 Value 都有 Kind 方法返回一个常量来表示类型:Uint、Float64、Slice 等等。同样 Value 有叫做 Int 和 Float 的方法可以获取存储在内部的值(跟 int64 和 float64 一样)

  1. const (
  2. Invalid Kind = iota
  3. Bool
  4. Int
  5. Int8
  6. Int16
  7. Int32
  8. Int64
  9. Uint
  10. Uint8
  11. Uint16
  12. Uint32
  13. Uint64
  14. Uintptr
  15. Float32
  16. Float64
  17. Complex64
  18. Complex128
  19. Array
  20. Chan
  21. Func
  22. Interface
  23. Map
  24. Ptr
  25. Slice
  26. String
  27. Struct
  28. UnsafePointer
  29. )

对于 float64 类型的变量 x,如果 v:=reflect.ValueOf(x),那么 v.Kind() 返回 reflect.Float64 ,所以下面的表达式是 true
v.Kind() == reflect.Float64

Kind 总是返回底层类型:

  1. type MyInt int
  2. var m MyInt = 5
  3. v := reflect.ValueOf(m)

方法 v.Kind() 返回 reflect.Int

变量 v 的 Interface() 方法可以得到还原(接口)值,所以可以这样打印 v 的值:fmt.Println(v.Interface())

尝试运行下面的代码:

示例 11.11 reflect1.go

  1. // blog: Laws of Reflection
  2. package main
  3. import (
  4. "fmt"
  5. "reflect"
  6. )
  7. func main() {
  8. var x float64 = 3.4
  9. fmt.Println("type:", reflect.TypeOf(x))
  10. v := reflect.ValueOf(x)
  11. fmt.Println("value:", v)
  12. fmt.Println("type:", v.Type())
  13. fmt.Println("kind:", v.Kind())
  14. fmt.Println("value:", v.Float())
  15. fmt.Println(v.Interface())
  16. fmt.Printf("value is %5.2e\n", v.Interface())
  17. y := v.Interface().(float64)
  18. fmt.Println(y)
  19. }

输出:

  1. type: float64
  2. value: 3.4
  3. type: float64
  4. kind: float64
  5. value: 3.4
  6. 3.4
  7. value is 3.40e+00
  8. 3.4

x 是一个 float64 类型的值,reflect.ValueOf(x).Float() 返回这个 float64 类型的实际值;同样的适用于 Int(), Bool(), Complex(), String()

11.10.2 通过反射修改 (设置) 值

继续前面的例子(参阅 11.9 reflect2.go),假设我们要把 x 的值改为 3.1415。Value 有一些方法可以完成这个任务,但是必须小心使用:v.SetFloat(3.1415)

这将产生一个错误:reflect.Value.SetFloat using unaddressable value

为什么会这样呢?问题的原因是 v 不是可设置的(这里并不是说值不可寻址)。是否可设置是 Value 的一个属性,并且不是所有的反射值都有这个属性:可以使用 CanSet() 方法测试是否可设置。

在例子中我们看到 v.CanSet() 返回 false: settability of v: false

v := reflect.ValueOf(x) 函数通过传递一个 x 拷贝创建了 v,那么 v 的改变并不能更改原始的 x。要想 v 的更改能作用到 x,那就必须传递 x 的地址 v = reflect.ValueOf(&x)

通过 Type () 我们看到 v 现在的类型是 *float64 并且仍然是不可设置的。

要想让其可设置我们需要使用 Elem() 函数,这间接的使用指针:v = v.Elem()

现在 v.CanSet() 返回 true 并且 v.SetFloat(3.1415) 设置成功了!

示例 11.12 reflect2.go

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func main() {
  7. var x float64 = 3.4
  8. v := reflect.ValueOf(x)
  9. // setting a value:
  10. // v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable value
  11. fmt.Println("settability of v:", v.CanSet())
  12. v = reflect.ValueOf(&x) // Note: take the address of x.
  13. fmt.Println("type of v:", v.Type())
  14. fmt.Println("settability of v:", v.CanSet())
  15. v = v.Elem()
  16. fmt.Println("The Elem of v is: ", v)
  17. fmt.Println("settability of v:", v.CanSet())
  18. v.SetFloat(3.1415) // this works!
  19. fmt.Println(v.Interface())
  20. fmt.Println(v)
  21. }

输出:

  1. settability of v: false
  2. type of v: *float64
  3. settability of v: false
  4. The Elem of v is: <float64 Value>
  5. settability of v: true
  6. 3.1415
  7. <float64 Value>

反射中有些内容是需要用地址去改变它的状态的。

11.10.3 反射结构体

有些时候需要反射一个结构体类型。NumField() 方法返回结构体内的字段数量;通过一个 for 循环用索引取得每个字段的值 Field(i)

我们同样能够调用签名在结构体上的方法,例如,使用索引 n 来调用:Method(n).Call(nil)

示例 11.13 reflect_struct.go

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type NotknownType struct {
  7. s1, s2, s3 string
  8. }
  9. func (n NotknownType) String() string {
  10. return n.s1 + " - " + n.s2 + " - " + n.s3
  11. }
  12. // variable to investigate:
  13. var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}
  14. func main() {
  15. value := reflect.ValueOf(secret) // <main.NotknownType Value>
  16. typ := reflect.TypeOf(secret) // main.NotknownType
  17. // alternative:
  18. //typ := value.Type() // main.NotknownType
  19. fmt.Println(typ)
  20. knd := value.Kind() // struct
  21. fmt.Println(knd)
  22. // iterate through the fields of the struct:
  23. for i := 0; i < value.NumField(); i++ {
  24. fmt.Printf("Field %d: %v\n", i, value.Field(i))
  25. // error: panic: reflect.Value.SetString using value obtained using unexported field
  26. //value.Field(i).SetString("C#")
  27. }
  28. // call the first method, which is String():
  29. results := value.Method(0).Call(nil)
  30. fmt.Println(results) // [Ada - Go - Oberon]
  31. }

输出:

  1. main.NotknownType
  2. struct
  3. Field 0: Ada
  4. Field 1: Go
  5. Field 2: Oberon
  6. [Ada - Go - Oberon]

但是如果尝试更改一个值,会得到一个错误:

  1. panic: reflect.Value.SetString using value obtained using unexported field

这是因为结构体中只有被导出字段(首字母大写)才是可设置的;来看下面的例子:

示例 11.14 reflect_struct2.go

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. type T struct {
  7. A int
  8. B string
  9. }
  10. func main() {
  11. t := T{23, "skidoo"}
  12. s := reflect.ValueOf(&t).Elem()
  13. typeOfT := s.Type()
  14. for i := 0; i < s.NumField(); i++ {
  15. f := s.Field(i)
  16. fmt.Printf("%d: %s %s = %v\n", i,
  17. typeOfT.Field(i).Name, f.Type(), f.Interface())
  18. }
  19. s.Field(0).SetInt(77)
  20. s.Field(1).SetString("Sunset Strip")
  21. fmt.Println("t is now", t)
  22. }

输出:

  1. 0: A int = 23
  2. 1: B string = skidoo
  3. t is now {77 Sunset Strip}

附录 37 深入阐述了反射概念。