Go语言学习之reflect | 青训营笔记

101 阅读2分钟

这是我参与「第五届青训营 」笔记创作活动的第16天。

最近各大厂陆续开始收暑期实习的简历了,作为Go语言选手自然要好好学习一下Go语言相关知识。

reflect,中文译为“反射”,是Go语言中的一种特性。它可以在运行时动态地获取对象的一些信息。这样说起来有点抽象,不妨参考例子来理解。

反射在Go语言中最广泛的用处之一就是用于获取结构体成员的tag,例如:

type Test struct {
   Name string `json:"username"`
   Age  int    `json:"userage"`
}

你在用json序列化该结构体的时候,想把Name成员命名为username,把Age成员命名为userage。那么你需要在对应成员后面加上json:"username"这样的语句,这就称为tag。

func main() {
   t := Test{"zhang3", 17}
   var interf interface{}
   interf = t
   jsonStr, _ := json.Marshal(interf)
   tType := reflect.TypeOf(interf)
   fmt.Println("Tag of Name:", tType.Field(0).Tag.Get("json"))
   fmt.Println("Tag of Age:", tType.Field(1).Tag.Get("json"))
   fmt.Println(string(jsonStr))
}

以上程序的输出为:

Tag of Name: username
Tag of Age: userage
{"username":"zhang3","userage":17}

通过看json.Marshal()的定义我们可以发现,它接受的是一个空接口。也就是说,我们随便传一个结构体进去,它都能接受。因此,运行时传给它的结构体不同,那么tag也可能不同,所以我们需要能在运行时动态地获取关于结构体的一些信息,比如说tag。

再进一步,包括空接口(有点特殊)在内的每个接口,都有自己的静态类型和动态类型。静态类型就是写在代码中的定义,比如说以上的var interf interface{}一句;而动态类型就是运行时其接受的类型,比如在把t赋值给interf后,后者的动态类型就是Test了。所谓的反射,就是去获取关于动态类型的信息。

那既然知道了动态类型,我们就可以把一个接口还原成它原来的类型,这个过程就叫做类型断言。方法就是interf.(Test),点加上一对括号,中间填上断言的类型;或是使用switch一次判断多种。

func main() {
   t := Test{"zhang3", 17}
   var interf interface{}
   interf = t
   o := interf.(Test)
   switch interf.(type) {
   case int:
      fmt.Println("int")
   case Test:
      fmt.Println("Test")
   }
   fmt.Println(o)
}

输出:

Test
{zhang3 17}

也可以直接对值进行修改。注意这里的interf = &t必须是t的地址,原理类似于C的指针传递才能在函数里修改外部实参的值,值传递(经过了复制)是不行的。

func main() {
   t := Test{"zhang3", 17}
   var interf interface{}
   interf = &t
   v := reflect.ValueOf(interf).Elem()
   v.Field(0).SetString("li4")
   fmt.Println(t)
}

输出:

{li4 17}