Go 中Reflect进阶篇

370 阅读3分钟

前面写过一篇关于Reflect入门文章 传送门, 这里再写一篇进阶篇。 之前的入门篇不能解决当前问题,因此有查阅了大量资料决定来个进阶篇。

当前遇到的问题是:希望遍历某个packge下 某个类型的 所有 非私有方法 注册为请求的方法。实现接口开发与请求层解耦

反射概述

Go语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。

反射机制设计目标之一就是任何非反射操作都可以通过反射机制来完成。尽管在Go中没有做到100%实现。但是目前大部分非反射操作都可以通过反射机制来完成。另一方面,通过反射,也可以完成一些使用非反射操作不可能完成的操作。

reflect.Type

通过reflect.TypeOf函数,可以从任何一个非接口类型的值创建一个reflect.Type值。

此reflect.Type值表示着此非接口值的类型。通过此值,可以得到很多非接口类型的信息。

当然,也可以将一个接口值传递给一个reflect.TypeOf函数调用,但是此调用将返回一个表示着此接口值的动态类型的reflect.Type值。 实际上,reflect.TypeOf函数的唯一参数的类型为interface{}。

关于Type的接口定义说明:快速入口

package main

import "fmt"
import "reflect"

func main() {
   type ArrayType = [1]int
   var c <-chan map[ArrayType][]byte

   tc := reflect.TypeOf(c)
   fmt.Println(tc.Kind()) // chan
   // 获得通道方向
   fmt.Println(tc.ChanDir()) // <-chan
   tm := tc.Elem()
   // Key returns a map type's key type.  It panics if the type's Kind is not Map.
   //Elem returns a type's element type.  It panics if the type's Kind is not Array, Chan, Map, Pointer, or Slice.
   ta, tb := tm.Key(), tm.Elem()
   fmt.Println(tm.Kind(), ta.Kind(), tb.Kind()) // map array slice

   tx, ty := ta.Elem(), tb.Elem()
   // byte是uint8类型的别名。
   fmt.Println(tx.Kind(), ty.Kind()) // int uint8
   fmt.Println(tx.Bits(), ty.Bits()) // 16 8
   fmt.Println(tx.ConvertibleTo(ty)) // true
   fmt.Println(tb.ConvertibleTo(ta)) // false

   // 切片类型和映射类型都是不可比较类型。
   fmt.Println(tb.Comparable()) // false
   fmt.Println(tm.Comparable()) // false
   fmt.Println(ta.Comparable()) // true
   fmt.Println(tc.Comparable()) // true
}

Elem() 是获得 指向类型Type,跟有些博主认为的和 * 取值操作是不一样的。

通过反射列出一个类型的所有方法和一个结构体类型的所有(导出和非导出)字段的类型。 我们也可以通过反射列出一个函数类型的各个输入参数和返回结果类型。

package main

import "fmt"
import "reflect"

type T []interface{ m() }

func (T) m() {}

func main() {
   tp := reflect.TypeOf(new(interface{}))
   tt := reflect.TypeOf(T{})
   fmt.Println(tp.Kind(), tt.Kind()) // ptr slice

   // 使用间接的方法得到表示两个接口类型的reflect.Type值。
   ti, tim := tp.Elem(), tt.Elem()
   fmt.Println(ti.Kind(), tim.Kind()) // interface interface

   fmt.Println(tt.Implements(tim))  // true
   fmt.Println(tp.Implements(tim))  // false
   fmt.Println(tim.Implements(tim)) // true

   // 所有的类型都实现了任何空接口类型。
   fmt.Println(tp.Implements(ti))  // true
   fmt.Println(tt.Implements(ti))  // true
   fmt.Println(tim.Implements(ti)) // true
   fmt.Println(ti.Implements(ti))  // true
}

下面例子是获得某个类型下的所有方法,是需要理解的关键。

通过反射列出一个类型的所有方法和一个结构体类型的所有(导出和非导出)字段的类型。

通过反射列出一个函数类型的各个输入参数和返回结果类型。

package main

import "fmt"
import "reflect"

type F func(string, int) bool

func (f F) m(s string) bool {
   return f(s, 32)
}
func (f F) M() {}

type I interface {
   m(s string) bool
   M()
}

func main() {
   var x struct {
      F F
      i I
   }
   tx := reflect.TypeOf(x)
   fmt.Println(tx.Kind())     // struct
   fmt.Println(tx.NumField()) // 2

   fmt.Println(tx.Field(0).Name) // F
   fmt.Println(tx.Field(1).Name) // i

   // 包路径(PkgPath)是非导出字段(或者方法)的内在属性。
   fmt.Println(tx.Field(0).PkgPath) //
   fmt.Println(tx.Field(1).PkgPath) // main

   tf, ti := tx.Field(0).Type, tx.Field(1).Type
   fmt.Println(tf.Kind())               // func
   fmt.Println(tf.IsVariadic())         // false
   fmt.Println(tf.NumIn(), tf.NumOut()) // 2 1
   t0, t1, t2 := tf.In(0), tf.In(1), tf.Out(0)
   // 下一行打印出:string int bool
   fmt.Println(t0.Kind(), t1.Kind(), t2.Kind())
    
   fmt.Println(tf.NumMethod(), ti.NumMethod()) // 1 2
   fmt.Println(tf.Method(0).Name)              // M
   fmt.Println(ti.Method(1).Name)              // m
   // 接口私有方法可以被遍历出来,但是结构体却不可以
   _, ok1 := tf.MethodByName("m")
   _, ok2 := ti.MethodByName("m")
   fmt.Println(ok1, ok2) // false true
}

NumMethod 和 MethodByName 他们对于接口来说导出和非导出方法都能统计获取到,但是对于类型来说只能获得其导出方法。

我们可以通过反射来检视结构体字段的标签信息。 结构体字段标签的类型为reflect.StructTag,它的方法Get和Lookup用来检视字段标签中的键值对。 一个例子:

通过反射

来检视结构体字段标签信息,结构体字段标签的类型为 reflect.StructTag, 他的方法Get 和 Lookup用来检视这段标签中键值对。

package main

import "fmt"
import "reflect"

type T struct {
   X    int  `max:"99" min:"0" default:"0"`
   Y, Z bool `optional:"yes"`
}

func main() {
   t := reflect.TypeOf(T{})
   x := t.Field(0).Tag
   y := t.Field(1).Tag
   z := t.Field(2).Tag
   fmt.Println(reflect.TypeOf(x)) // reflect.StructTag
   // v的类型为string
   v, present := x.Lookup("max")
   fmt.Println(len(v), present)      // 2 true
   fmt.Println(x.Get("max"))         // 99
   fmt.Println(x.Lookup("optional")) //  false
   fmt.Println(y.Lookup("optional")) // yes true
   fmt.Println(z.Lookup("optional")) // yes true
}

关于tag有这样的几个注意点:

  • 键值对中的键不能包含空格、双引号 和 冒号。

  • 为了形成键值对,所设想的键值对形式中的冒号的后面不能紧跟着空格字符。所以 `optional: "yes"`不形成键值对。

  • 键值对中的值中的空格不会被忽略。所以 `json:"author, omitempty"` `json:" author,omitempty"` `json:"author,omitempty"`各不相同。

  • 每个字段标签应该呈现为单行也是要求。

Reflect.Value

官方库地址:快速入口

调用reflect.ValueOf函数,从一个非接口类型的值创建一个reflect.Value值。 此reflect.Value值代表着此非接口值。 和reflect.TypeOf函数类似,reflect.ValueOf函数也只有一个interface{}类型的参数。

将一个接口值传递给一个reflect.ValueOf函数调用时,此调用返回的是代表着此接口值的动态值的一个reflect.Value值。 必须通过间接的途径获得一个代表一个接口值的reflect.Value值。

【可以这么理解,获得的是运行的地址,需要用Elem()取得他的指针,并进行赋值】

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

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

package main

import "fmt"
import "reflect"

func main() {
   n := 123
   p := &n
   vp := reflect.ValueOf(p)
   fmt.Println(vp.CanSet(), vp.CanAddr()) // false false
   vn := vp.Elem()                        // 取得vp的底层指针值引用的值的代表值
   fmt.Println(vn.CanSet(), vn.CanAddr()) // true true
   vn.Set(reflect.ValueOf(789))           // <=> vn.SetInt(789)
   fmt.Println(n)                         // 789
   vnn := reflect.ValueOf(n)
   fmt.Println(vnn.CanSet(), vnn.CanAddr()) // false false
}

获取一个代表着一个指针所引用的值的reflect.Value值的两个方法:

  1. 通过调用代表着此指针值的reflect.Value值的Elem方法。

  2. 将代表着此指针值的reflect.Value值的传递给一个reflect.Indirect函数调用。 (如果传递给一个reflect.Indirect函数调用的实参不代表着一个指针值,则此调用返回此实参的一个复制。)

package main

import "fmt"
import "reflect"

func main() {
   var s struct {
      X interface{} // 一个导出字段
      y interface{} // 一个非导出字段
   }
   vp := reflect.ValueOf(&s)
   // 如果vp代表着一个指针,下一行等价于"vs := vp.Elem()"。
   vs := reflect.Indirect(vp)
   // vx和vy都各自代表着一个接口值。
   vx, vy := vs.Field(0), vs.Field(1)
   fmt.Println(vx.CanSet(), vx.CanAddr()) // true true
   // vy is addressable but not modifiable.
   fmt.Println(vy.CanSet(), vy.CanAddr()) // false true
   vb := reflect.ValueOf(123)
   vx.Set(vb) // okay, 因为vx代表的值是可修改的。
   // vy.Set(vb)  // 会造成恐慌,因为vy代表的值是不可修改的。
   fmt.Println(s)                      // {123 <nil>}
   fmt.Println(vx.IsNil(), vy.IsNil()) // false true
}

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

package main

import "fmt"
import "reflect"

func InvertSlice(args []reflect.Value) []reflect.Value {
   inSlice, n := args[0], args[0].Len()
   outSlice := reflect.MakeSlice(inSlice.Type(), 0, n)
   for i := n - 1; i >= 0; i-- {
      element := inSlice.Index(i)
      outSlice = reflect.Append(outSlice, element)
   }
   return []reflect.Value{outSlice}
}

func Bind(p interface{},
   f func([]reflect.Value) []reflect.Value) {
   // invert代表着一个函数值。
   invert := reflect.ValueOf(p).Elem() 
   invert.Set(reflect.MakeFunc(invert.Type(), f))
}

func main() {
   var invertInts func([]int) []int
   Bind(&invertInts, InvertSlice)
   fmt.Println(invertInts([]int{2, 3, 5})) // [5 3 2]

   var invertStrs func([]string) []string
   Bind(&invertStrs, InvertSlice)
   fmt.Println(invertStrs([]string{"Go", "C"})) // [C Go]
}

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

package main

import "fmt"
import "reflect"

type T struct {
   A, b int
}

func (t T) AddSubThenScale(n int) (int, int) {
   return n * (t.A + t.b), n * (t.A - t.b)
}

func main() {
   t := T{5, 2}
   vt := reflect.ValueOf(t)
   vm := vt.MethodByName("AddSubThenScale")
   results := vm.Call([]reflect.Value{reflect.ValueOf(3)}) // 调用方法是
   fmt.Println(results[0].Int(), results[1].Int())         // 21 9

   neg := func(x int) int {
      return -x
   }
   vf := reflect.ValueOf(neg)
   fmt.Println(vf.Call(results[:1])[0].Int()) // -21
   fmt.Println(vf.Call([]reflect.Value{
      vt.FieldByName("A"), // 如果是字段b,则造成恐慌
   })[0].Int()) // -5
}

非导出结构体字段值不能用做反射函数调用中的实参。 如果上例中的vt.FieldByName("A")被替换为vt.FieldByName("b"),则将产生一个panic。

文章引用