这是我参与「第五届青训营 」笔记创作活动的第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}