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

接口的值包含一个 typevalue。反射可以从接口值反射到对象,也可以从对象反射回接口值。

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

  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

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

  1. fmt.Println(v.Interface())

尝试运行下面的代码:

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

输出结果:

  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()

通过反射修改(设置)值

继续看上面的例子,假设我们要把 x 的值改为 3.1415Value 有一些方法可以完成这个任务,但是必须小心使用:

  1. v.SetFloat(3.1415)。

这将产生一个错误:

  1. reflect.Value.SetFloat using unaddressable value

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

在下面的例子(reflect2.go)中, v.CanSet() 会返回 falsesettability 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) 设置成功了!

示例 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>

通过上面的例子可以发现,反射中有些内容是需要用地址去改变它的状态的。

反射结构

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

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

示例 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

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

示例 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}