反射

Go是一门具有良好反射支持的静态语言。本文将解释reflect标准库包中提供的反射功能。

在阅读本剩下的部分之前,最好先阅读Go类型系统概述接口两篇文章。

反射概述

上一篇文章中,我们得知目前Go缺少自定义范型支持。Go中提供的的反射功能带来了很多动态特性,从而从一定程度上弥补了Go缺少自定义范型而导致的不便(尽管反射比范型的运行效率低)。很多标准库,比如fmt和很多encoding包,均十分依赖于反射机制。

我们可以通过reflect库包中TypeValue两个类型提供的功能来观察不同的Go值。下面紧接着的两节将介绍如何使用这两个类型。

Go反射机制设计的目标之一是任何非反射操作都可以通过反射机制来完成。由于各种各样的原因,此目标目前(Go 1.13)并没有得到100%的实现。但是,目前大部分的非反射操作都可以通过反射机制来完成。另一方面,通过反射,我们也可以完成一些使用非反射操作不可能完成的操作。不能或者只能通过反射完成的操作将在下面的讲解中提及。

reflect.Type类型和值

通过调用reflect.TypeOf函数,我们可以从一个任何非接口类型的值创建一个reflect.Type值。此reflect.Type值表示着此非接口值的类型。通过此值,我们可以得到很多此非接口类型的信息。当然,我们也可以将一个接口值传递给一个reflect.TypeOf函数调用,但是此调用将返回一个表示着此接口值的动态类型的reflect.Type值。实际上,reflect.TypeOf函数的唯一参数的类型为interface{}reflect.TypeOf函数将总是返回一个表示着此唯一接口参数值的动态类型的reflect.Type值。那如何得到一个表示着某个接口类型的reflect.Type值呢?我们必须通过下面将要介绍的一些间接途径来达到这一目的。

类型reflect.Type为一个接口类型,它指定了若干方法。通过这些方法,我们能够观察到一个reflect.Type值所表示的Go类型的各种信息。这些方法中的有的适用于所有种类的类型,有的只适用于一种或几种类型。通过不合适的reflect.Type属主值调用某个方法将在运行时产生一个恐慌。请阅读reflect代码库中各个方法的文档来获取如何正确地使用这些方法。 一个例子:

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. func main() {
  5. type A = [16]int16
  6. var c <-chan map[A][]byte
  7. tc := reflect.TypeOf(c)
  8. fmt.Println(tc.Kind()) // chan
  9. fmt.Println(tc.ChanDir()) // <-chan
  10. tm := tc.Elem()
  11. ta, tb := tm.Key(), tm.Elem()
  12. fmt.Println(tm.Kind(), ta.Kind(), tb.Kind()) // map array slice
  13. tx, ty := ta.Elem(), tb.Elem()
  14. // byte是uint8类型的别名。
  15. fmt.Println(tx.Kind(), ty.Kind()) // int16 uint8
  16. fmt.Println(tx.Bits(), ty.Bits()) // 16 8
  17. fmt.Println(tx.ConvertibleTo(ty)) // true
  18. fmt.Println(tb.ConvertibleTo(ta)) // false
  19. // 切片类型和映射类型都是不可比较类型。
  20. fmt.Println(tb.Comparable()) // false
  21. fmt.Println(tm.Comparable()) // false
  22. fmt.Println(ta.Comparable()) // true
  23. fmt.Println(tc.Comparable()) // true
  24. }

目前,Go支持26种类型。 在上面这个例子中,我们使用方法Elem来得到某些类型的元素类型。实际上,此方法也可以用来得到一个指针类型的基类型。一个例子:

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. type T []interface{m()}
  5. func (T) m() {}
  6. func main() {
  7. tp := reflect.TypeOf(new(interface{}))
  8. tt := reflect.TypeOf(T{})
  9. fmt.Println(tp.Kind(), tt.Kind()) // ptr slice
  10. // 使用间接的方法得到表示两个接口类型的reflect.Type值。
  11. ti, tim := tp.Elem(), tt.Elem()
  12. fmt.Println(ti.Kind(), tim.Kind()) // interface interface
  13. fmt.Println(tt.Implements(tim)) // true
  14. fmt.Println(tp.Implements(tim)) // false
  15. fmt.Println(tim.Implements(tim)) // true
  16. // 所有的类型都实现了任何空接口类型。
  17. fmt.Println(tp.Implements(ti)) // true
  18. fmt.Println(tt.Implements(ti)) // true
  19. fmt.Println(tim.Implements(ti)) // true
  20. fmt.Println(ti.Implements(ti)) // true
  21. }

上面这个例子同时也展示了如何通过间接的途径得到一个表示一个接口类型的reflect.Type值。 我们可以通过反射列出一个类型的所有(导出)方法和一个结构体类型的所有字段。我们也可以通过反射列出一个函数类型的各个输入参数和返回结果类型。

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. type F func(string, int) bool
  5. func (f F) Validate(s string) bool {
  6. return f(s, 32)
  7. }
  8. func main() {
  9. var x struct {
  10. n int
  11. f F
  12. }
  13. tx := reflect.TypeOf(x)
  14. fmt.Println(tx.Kind()) // struct
  15. fmt.Println(tx.NumField()) // 2
  16. tf := tx.Field(1).Type
  17. fmt.Println(tf.Kind()) // func
  18. fmt.Println(tf.IsVariadic()) // false
  19. fmt.Println(tf.NumIn(), tf.NumOut()) // 2 1
  20. fmt.Println(tf.NumMethod()) // 1
  21. ts, ti, tb := tf.In(0), tf.In(1), tf.Out(0)
  22. fmt.Println(ts.Kind(), ti.Kind(), tb.Kind()) // string int bool
  23. }

注意:reflect.Type.NumMethod方法只返回一个类型的所有导出的方法的个数。 reflect代码包也提供了一些其它函数来动态地创建出来一些非定义组合类型。

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. func main() {
  5. ta := reflect.ArrayOf(5, reflect.TypeOf(123))
  6. fmt.Println(ta) // [5]int
  7. tc := reflect.ChanOf(reflect.SendDir, ta)
  8. fmt.Println(tc) // chan<- [5]int
  9. tp := reflect.PtrTo(ta)
  10. fmt.Println(tp) // *[5]int
  11. ts := reflect.SliceOf(tp)
  12. fmt.Println(ts) // []*[5]int
  13. tm := reflect.MapOf(ta, tc)
  14. fmt.Println(tm) // map[[5]int]chan<- [5]int
  15. tf := reflect.FuncOf([]reflect.Type{ta},
  16. []reflect.Type{tp, tc}, false)
  17. fmt.Println(tf) // func([5]int) (*[5]int, chan<- [5]int)
  18. tt := reflect.StructOf([]reflect.StructField{
  19. {Name: "Age", Type: reflect.TypeOf("abc")},
  20. })
  21. fmt.Println(tt) // struct { Age string }
  22. fmt.Println(tt.NumField()) // 1
  23. }

上面的例子并未展示和reflect.Type相关的的所有函数和方法。请阅读reflect标准库代码包的文档以获取如何使用这些函数和方法。

注意,到目前为止(Go 1.13),我们无法通过反射动态创建一个接口类型。这是Go反射目前的一个限制。

另一个限制是使用反射动态创建结构体类型的时候可能会有各种不完美的情况出现。

第三个限制是我们无法通过反射来声明一个新的类型。

reflect.Value类型和值

类似的,我们可以通过调用reflect.ValueOf函数,从一个非接口类型的值创建一个reflect.Value值。此reflect.Value值代表着此非接口值。和reflect.TypeOf函数类似,reflect.ValueOf函数也只有一个interface{}类型的参数。当我们将一个接口值传递给一个reflect.ValueOf函数调用时,此调用返回的是代表着此接口值的动态值的一个reflect.Value值。我们必须通过间接的途径获得一个代表一个接口值的reflect.Value值。

被一个reflect.Value值代表着的值常称为此reflect.Value值的底层值(underlying value)。

reflect.Value类型有很多方法。我们可以调用这些方法来观察和操纵一个reflect.Value属主值表示的Go值。这些方法中的有些适用于所有种类类型的值,有些只适用于一种或几种类型的值。通过不合适的reflect.Value属主值调用某个方法将在运行时产生一个恐慌。请阅读reflect代码库中各个方法的文档来获取如何正确地使用这些方法。

一个reflect.Value值的CanSet方法将返回此reflect.Value值代表的Go值是否可以被修改(可以被赋值)。如果一个Go值可以被修改,则我们可以调用对应的reflect.Value值的Set方法来修改此Go值。注意:reflect.ValueOf函数直接返回的reflect.Value值都是不可修改的。 一个例子:

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. func main() {
  5. n := 123
  6. p := &n
  7. vp := reflect.ValueOf(p)
  8. fmt.Println(vp.CanSet(), vp.CanAddr()) // false false
  9. vn := vp.Elem() // 取得vp的底层指针值引用的值的代表值
  10. fmt.Println(vn.CanSet(), vn.CanAddr()) // true true
  11. vn.Set(reflect.ValueOf(789)) // <=> vn.SetInt(789)
  12. fmt.Println(n) // 789
  13. }

一个结构体值的非导出字段不能通过反射来修改。

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. func main() {
  5. var s struct {
  6. X interface{} // 一个导出字段
  7. y interface{} // 一个非导出字段
  8. }
  9. vp := reflect.ValueOf(&s)
  10. // 如果vp代表着一个指针,下一行等价于"vs := vp.Elem()"。
  11. vs := reflect.Indirect(vp)
  12. // vx和vy都各自代表着一个接口值。
  13. vx, vy := vs.Field(0), vs.Field(1)
  14. fmt.Println(vx.CanSet(), vx.CanAddr()) // true true
  15. // vy is addressable but not modifiable.
  16. fmt.Println(vy.CanSet(), vy.CanAddr()) // false true
  17. vb := reflect.ValueOf(123)
  18. vx.Set(vb) // okay, 因为vx代表的值是可修改的。
  19. // vy.Set(vb) // 会造成恐慌,因为vy代表的值是不可修改的。
  20. fmt.Println(s) // {123 <nil>}
  21. fmt.Println(vx.IsNil(), vy.IsNil()) // false true
  22. }

上例中同时也展示了如何间接地获取底层值为接口值的reflect.Value值。 从上两例中,我们可以得知有两种方法获取一个代表着一个指针所引用着的值的reflect.Value值:

  • 通过调用代表着被此指针值的reflect.Value值的Elem方法。
  • 将代表着被此指针值的reflect.Value值的传递给一个reflect.Indirect函数调用。(如果传递给一个reflect.Indirect函数调用的实参不代表着一个指针值,则此调用返回此实参的一个复制。)注意:reflect.Value.Elem方法也可以用来获取一个代表着一个接口值的动态值的reflect.Value值,比如下例中所示。
  1. package main
  2. import "fmt"
  3. import "reflect"
  4. func main() {
  5. var z = 123
  6. var y = &z
  7. var x interface{} = y
  8. v := reflect.ValueOf(&x)
  9. vx := v.Elem()
  10. vy := vx.Elem()
  11. vz := vy.Elem()
  12. vz.Set(reflect.ValueOf(789))
  13. fmt.Println(z) // 789
  14. }

reflect标准库包中也提供了一些对应着内置函数或者各种非反射功能的函数。下面这个例子展示了如何利用这些函数将一个自定义范型函数绑定到不同的类型的函数值上。

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. func InvertSlice(args []reflect.Value) (result []reflect.Value) {
  5. inSlice, n := args[0], args[0].Len()
  6. outSlice := reflect.MakeSlice(inSlice.Type(), 0, n)
  7. for i := n-1; i >= 0; i-- {
  8. element := inSlice.Index(i)
  9. outSlice = reflect.Append(outSlice, element)
  10. }
  11. return []reflect.Value{outSlice}
  12. }
  13. func Bind(p interface{}, f func ([]reflect.Value) []reflect.Value) {
  14. // invert代表着一个函数值。
  15. invert := reflect.ValueOf(p).Elem()
  16. invert.Set(reflect.MakeFunc(invert.Type(), f))
  17. }
  18. func main() {
  19. var invertInts func([]int) []int
  20. Bind(&invertInts, InvertSlice)
  21. fmt.Println(invertInts([]int{2, 3, 5})) // [5 3 2]
  22. var invertStrs func([]string) []string
  23. Bind(&invertStrs, InvertSlice)
  24. fmt.Println(invertStrs([]string{"Go", "C"})) // [C Go]
  25. }

如果一个reflect.Value值的底层值为一个函数值,则我们可以调用此reflect.Value值的Call方法来调用此函数。每个Call方法调用接受一个[]reflect.Value类型的参数(表示传递给相应函数调用的各个实参)并返回一个同类型结果(表示相应函数调用返回的各个结果)。

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. type T struct {
  5. A, b int
  6. }
  7. func (t T) AddSubThenScale(n int) (int, int) {
  8. return n * (t.A + t.b), n * (t.A - t.b)
  9. }
  10. func main() {
  11. t := T{5, 2}
  12. vt := reflect.ValueOf(t)
  13. vm := vt.MethodByName("AddSubThenScale")
  14. results := vm.Call([]reflect.Value{reflect.ValueOf(3)})
  15. fmt.Println(results[0].Int(), results[1].Int()) // 21 9
  16. neg := func(x int) int {
  17. return -x
  18. }
  19. vf := reflect.ValueOf(neg)
  20. fmt.Println(vf.Call(results[:1])[0].Int()) // -21
  21. fmt.Println(vf.Call([]reflect.Value{
  22. vt.FieldByName("A"), // 如果是字段b,则造成恐慌
  23. })[0].Int()) // -5
  24. }

请注意:非导出结构体字段值不能用做反射函数调用中的实参。如果上例中的vt.FieldByName("A")被替换为vt.FieldByName("b"),则将产生一个恐慌。 下面是一个使用映射反射值的例子。

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. func main() {
  5. vakueOf := reflect.ValueOf
  6. m := map[string]int{"Unix": 1973, "Windows": 1985}
  7. v := vakueOf(m)
  8. // 第二个实参为Value零值时,表示删除一个映射条目。
  9. v.SetMapIndex(vakueOf("Windows"), reflect.Value{})
  10. v.SetMapIndex(vakueOf("Linux"), vakueOf(1991))
  11. for i := v.MapRange(); i.Next(); {
  12. fmt.Println(i.Key(), "\t:", i.Value())
  13. }
  14. }

注意:方法reflect.Value.MapRange方法是从Go 1.12开始才支持的。 下面是一个使用通道反射值的例子。

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. func main() {
  5. c := make(chan string, 2)
  6. vc := reflect.ValueOf(c)
  7. vc.Send(reflect.ValueOf("C"))
  8. succeeded := vc.TrySend(reflect.ValueOf("Go"))
  9. fmt.Println(succeeded) // true
  10. succeeded = vc.TrySend(reflect.ValueOf("C++"))
  11. fmt.Println(succeeded) // false
  12. fmt.Println(vc.Len(), vc.Cap()) // 2 2
  13. vs, succeeded := vc.TryRecv()
  14. fmt.Println(vs.String(), succeeded) // C true
  15. vs, sentBeforeClosed := vc.Recv()
  16. fmt.Println(vs.String(), sentBeforeClosed) // Go false
  17. vs, succeeded = vc.TryRecv()
  18. fmt.Println(vs.String()) // <invalid Value>
  19. fmt.Println(succeeded) // false
  20. }

reflect.Value类型的TrySendTryRecv方法对应着只有一个case分支和一个default分支的select流程控制代码块。 我们可以使用reflect.Select函数在运行时刻来模拟具有不定case分支数量的select流程控制代码块。

  1. package main
  2. import "fmt"
  3. import "reflect"
  4. func main() {
  5. c := make(chan int, 1)
  6. vc := reflect.ValueOf(c)
  7. succeeded := vc.TrySend(reflect.ValueOf(123))
  8. fmt.Println(succeeded, vc.Len(), vc.Cap()) // true 1 1
  9. vSend, vZero := reflect.ValueOf(789), reflect.Value{}
  10. branches := []reflect.SelectCase{
  11. {Dir: reflect.SelectDefault, Chan: vZero, Send: vZero},
  12. {Dir: reflect.SelectRecv, Chan: vc, Send: vZero},
  13. {Dir: reflect.SelectSend, Chan: vc, Send: vSend},
  14. }
  15. selIndex, vRecv, closed := reflect.Select(branches)
  16. fmt.Println(selIndex) // 1
  17. fmt.Println(closed) // true
  18. fmt.Println(vRecv.Int()) // 123
  19. vc.Close()
  20. // 再模拟select流程控制代码块。因为vc已经关闭了,
  21. // 所以需将最后一个case分支去除,否则它可能会造成一个恐慌。
  22. selIndex, _, closed = reflect.Select(branches[:2])
  23. fmt.Println(selIndex, closed) // 1 false
  24. }

一些reflect.Value值可能表示着不合法的Go值。这样的值为reflect.Value类型的零值(即没有底层值的reflect.Value值)。

  1. package main
  2. import "reflect"
  3. import "fmt"
  4. func main() {
  5. var z reflect.Value // 一个reflect.Value零值
  6. fmt.Println(z) // <invalid reflect.Value>
  7. v := reflect.ValueOf((*int)(nil)).Elem()
  8. fmt.Println(v) // <invalid reflect.Value>
  9. fmt.Println(v == z) // true
  10. var i = reflect.ValueOf([]interface{}{nil}).Index(0)
  11. fmt.Println(i) // <nil>
  12. fmt.Println(i.Elem()) // <invalid reflect.Value>
  13. fmt.Println(i.Elem() == z) // true
  14. }

从上面的例子中,我们知道,使用空接口interface{}值做为中介,一个Go值可以转换为一个reflect.Value值。逆过程类似,通过调用一个reflect.Value值的Interface方法得到一个interface{}值,然后将此interface{}断言为原来的Go值。

  1. package main
  2. import "reflect"
  3. import "fmt"
  4. func main() {
  5. vx := reflect.ValueOf(123)
  6. vy := reflect.ValueOf("abc")
  7. vz := reflect.ValueOf([]bool{false, true})
  8. x := vx.Interface().(int)
  9. y := vy.Interface().(string)
  10. z := vz.Interface().([]bool)
  11. fmt.Println(x, y, z) // 123 abc [false true]
  12. }

从Go 1.13开始,我们可以使用Value.IsZero方法来查看一个值是否为零值。 上面这些例子并未触及到所有的和reflect.Value相关的函数和方法,请阅读reflect标准库包的文档以获取如何使用这些函数和方法。另外请注意Go细节101中提到的一些关于反射的细节

Go语言101项目目前同时托管在GithubGitlab上。欢迎各位在这两个项目中通过提交bug和PR的方式来改进完善Go语言101中的各篇文章。

本书微信公众号名称为"Go 101"。每个工作日此公众号将尽量发表一篇和Go语言相关的原创短文。各位如果感兴趣,可以搜索关注一下。

赞赏