学习使用Go反射的用法示例

2020-01-28 14:16:59丽君

什么是反射

大多数时候,Go中的变量,类型和函数非常简单直接。当需要一个类型、变量或者是函数时,可以直接定义它们:


type Foo struct {
 A int
 B string
}

var x Foo

func DoSomething(f Foo) {
 fmt.Println(f.A, f.B)
}

但是有时你希望在运行时使用变量的在编写程序时还不存在的信息。比如你正在尝试将文件或网络请求中的数据映射到变量中。或者你想构建一个适用于不同类型的工具。在这种情况下,你需要使用反射。反射使您能够在运行时检查类型。它还允许您在运行时检查,修改和创建变量,函数和结构体。

Go中的反射是基于三个概念构建的:类型,种类和值(Types Kinds Values)。标准库中的reflect包提供了 Go 反射的实现。

反射变量类型

首先让我们看一下类型。你可以使用反射来调用函数varType := reflect.TypeOf(var)来获取变量var的类型。这将返回类型为reflect.Type的变量,该变量具有获取定义时变量的类型的各种信息的方法集。下面我们来看一下常用的获取类型信息的方法。

我们要看的第一个方法是Name()。这将返回变量类型的名称。某些类型(例如切片或指针)没有名称,此方法会返回空字符串。

下一个方法,也是我认为第一个真正非常有用的方法是Kind()。Type是由Kind组成的---Kind 是切片,映射,指针,结构,接口,字符串,数组,函数,int或其他某种原始类型的抽象表示。要理解Type和Kind之间的差异可能有些棘手,但是请你以这种方式来思考。如果定义一个名为Foo的结构体,则Kind为struct,类型为Foo。

使用反射时要注意的一件事:反射包中的所有内容都假定你知道自己在做什么,并且如果使用不正确,许多函数和方法调用都会引起 panic。例如,如果你在reflect.Type上调用与当前类型不同的类型关联的方法,您的代码将会panic。

如果变量是指针,映射,切片,通道或数组变量,则可以使用varType.Elem()找出指向或包含的值的类型。

如果变量是结构体,则可以使用反射来获取结构体中的字段数,并从每个字段上获取reflect.StructField结构体。 reflection.StructField为您提供了字段的名称,标号,类型和结构体标签。其中标签信息对应reflect.StructTag类型的字符串,并且它提供了Get方法用于解析和根据特定key提取标签信息中的子串。

下面是一个简单的示例,用于输出各种变量的类型信息:


type Foo struct {
  A int `tag1:"First Tag" tag2:"Second Tag"`
  B string
}

func main() {
  sl := []int{1, 2, 3}
  greeting := "hello"
  greetingPtr := &greeting
  f := Foo{A: 10, B: "Salutations"}
  fp := &f

  slType := reflect.TypeOf(sl)
  gType := reflect.TypeOf(greeting)
  grpType := reflect.TypeOf(greetingPtr)
  fType := reflect.TypeOf(f)
  fpType := reflect.TypeOf(fp)

  examiner(slType, 0)
  examiner(gType, 0)
  examiner(grpType, 0)
  examiner(fType, 0)
  examiner(fpType, 0)
}

func examiner(t reflect.Type, depth int) {
  fmt.Println(strings.Repeat("t", depth), "Type is", t.Name(), "and kind is", t.Kind())
  switch t.Kind() {
  case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
    fmt.Println(strings.Repeat("t", depth+1), "Contained type:")
    examiner(t.Elem(), depth+1)
  case reflect.Struct:
    for i := 0; i < t.NumField(); i++ {
      f := t.Field(i)
      fmt.Println(strings.Repeat("t", depth+1), "Field", i+1, "name is", f.Name, "type is", f.Type.Name(), "and kind is", f.Type.Kind())
      if f.Tag != "" {
        fmt.Println(strings.Repeat("t", depth+2), "Tag is", f.Tag)
        fmt.Println(strings.Repeat("t", depth+2), "tag1 is", f.Tag.Get("tag1"), "tag2 is", f.Tag.Get("tag2"))
      }
    }
  }
}