所谓反射 (refection) 是指程序在运行过程中获取变量的类型、属性。 在 Golang 中,有时我们会看到 reflect.ValueOf()
或者 reflect.TypeOf()
这两个函数,这就是反射出一个变量的值和类型。 gPRC 的实现中也大量运用了反射。
本文主要介绍如何使用 reflect 包,关于 Go 内部是如何实现的将在下一篇文章中介绍。
TypeOf 和 ValueOf
先看一个最简单的例子
type User struct {
Name string
Age int
}
func main() {
u := User{"Dick", 18}
t := reflect.TypeOf(u)
v := reflect.ValueOf(u)
fmt.Printf("u type = %T, %v\n", u, u)
fmt.Printf("t type = %T, %v\n", t, t)
fmt.Printf("v type = %T, %v\n", v, v)
// 获取 v 的值
// v.Age , 错误,因为 v 是 reflect.Value 类型
// 正确方式如下
v1 := v.Interface().(User)
}
以上代码运行结果如下:
u type = main.User, {Dick 18}
t type = *reflect.rtype, main.User
v type = reflect.Value, {Dick 18}
可以看出,使用 reflect.ValueOf 返回的是一个 reflect.Value 类型,需要先通过 Interface() 函数返回成一个 interface 类型,再做强制类型转换。
同样道理,reflect.TypeOf 返回的是一个 reflect.rtype 类型,这个类型的值才是 User 结构体。
reflect.Value 还提供了 v.Type() 函数返回对应的 reflect.rtype
遍历结构体字段
当我们不知道一个结构体对象的是什么类型,有哪些成员函数的时候,可以使用 type.NumField()
和 type.NumMethod()
获取它的成员和函数。
例如下面这个例子
type User struct {
Name string
Age int
Addr string
}
func (u User) Foobar() {
fmt.Println("Foobar")
}
func (u *User) Barfoo() {
fmt.Println("Barfoo")
}
func reflectUnknownStruct(input interface{}) {
t := reflect.TypeOf(input)
v := reflect.ValueOf(input)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i).Interface()
fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}
func main() {
u := User{"Dick", 18, "earth"}
reflectUnknownStruct(u)
输出结果
Name: string = Dick
Age: int = 18
Addr: string = earth
Foobar: func(main.User)
值得注意的是,其中
func (u *User) Barfoo()
并没有被反射出来,因为它是*User
类型的方法。
那么该如何反射出 Barfoo()
呢? 目前还不知道,相信研究 reflect 的实现以后会发现答案。
通过 reflect.Value 设置变量的值
借用第一小节的例子,
type User struct {
Name string
Age int
}
func main() {
u := User{"Dick", 18}
// 注意,参数必须是指针才能修改其值
pointer := reflect.ValueOf(&u)
// 使用 Elem() 获得指针指向的 Value
newValue := pointer.Elem()
fmt.Println("type of pointer:", newValue.Type())
fmt.Println("CanSet() :", newValue.CanSet())
newValue.FieldByName("Age").SetInt(28)
fmt.Println("new value of u:", u)
}
Go 语言的函数调用都是传值的,所以我们得到的反射对象跟最开始的变量没有任何关系,直接修改反射对象无法改变原始变量。
要设置 Value 的值,前提要传入变量的指针,这样 ValueOf() 函数返回的 Value 所对应的类型是 reflect.Ptr (reflect 包内置定义了全部的基础类型,用户可以通过 Kind() 函数获取,这部分将在 reflect 包的实现中在解释 )
之后使用 Elem() 函数通过指针获得真正的 Value,此时获得 Value 代表的是 User 结构体,要想修改 Age,需要用 FieldByName 找到对应的 field。
通过 reflect.Value 调用结构体方法
type User struct {
Name string
Age int
}
func (u User) Rename(name string, age int) {
fmt.Println("name = ", name, ", age = ", age, ", original User.Name = ", u.Name)
}
func main() {
user := User{"Dick", 18}
getValue := reflect.ValueOf(user)
methodValue := getValue.MethodByName("Rename")
// 指定参数
args := []reflect.Value{reflect.ValueOf("Wood"), reflect.ValueOf(30)}
methodValue.Call(args)
}
基本的逻辑还是一样的,需要注意的是传递参数需要用 []reflect.Value{}
。
如果函数没有参数,创建一个长度为 0 的make([]reflect.Value, 0)
既可。
实际用途
说了这些 reflect 的用法后,那么日常开发过程中有哪些地方要用到 reflect 呢? 其中之一就是序列化/反序列化,类似 json Marshall 函数那样。
下面就用一个例子展示一些如何自己实现一个序列化函数。
package main
import (
"fmt"
"reflect"
"strconv"
)
type Person struct {
Name string `info:"name"`
Age int `info:"age"`
unexported string `info:"unexported"`
NoTag string
}
func UnMarshall(d interface{}, m map[string]string) {
typ := reflect.TypeOf(d)
// 首先判断传入参数的类型
if !(typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct) {
fmt.Printf("Should pass ptr to destination struct object.\n")
return
}
typ = typ.Elem()
value := reflect.ValueOf(d).Elem()
// 遍历每一个字段
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
// https://stackoverflow.com/questions/68573907/
// how-to-get-only-data-exported-fields-from-protobuf-struct-with-reflect
if !field.IsExported() {
fmt.Printf("`%s` is not exported, skip\n", field.Name)
continue
}
// 判断是否设置了这个tag
mytag := field.Tag.Get("info")
if mytag == "" {
fmt.Printf("tag `info` not exist in field: %s, ignore ...\n", field.Name)
continue
}
// check if input data has value
v := m[mytag]
if v == "" {
fmt.Printf("tag vaule is not found in the input data, ignore\n")
continue
}
// 根据类型来设置值
switch fieldType := field.Type.Kind(); fieldType {
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
typedV, _ := strconv.ParseInt(v, 10, 64)
value.Field(i).SetInt(typedV)
case reflect.String:
value.Field(i).SetString(v)
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
typedV, _ := strconv.ParseUint(v, 10, 64)
value.Field(i).SetUint(typedV)
case reflect.Bool:
value.Field(i).SetBool(v == "true")
default:
fmt.Printf("field type %s not support yet\n", fieldType)
}
}
}
func main() {
p := Person{}
d := map[string]string{
"name": "John",
"age": "16",
"unexported": "hello",
}
UnMarshall(&p, d)
fmt.Printf("%+v", p)
}