文章转载地址:https://www.flysnow.org/2017/06/13/go-in-action-go-reflect.html
1. TypeOf 和 ValueOf
在 Go 的反射定义中,任何接口都由两部分组成,一个是接口的具体类型,一个是具体类型对应的值。比如:
var i int = 3,因为 interface{} 可以表示任何类型,所以变量 i 可以转换成 interface{} ,所以可以把变量 i 当成一个
接口,那么这个变量在 Go 反射中的表示就是 <type,value> ,其中 value 为变量的值 3,type 为类型 int
在 Go 反射中,标准库为我们提供两种类型来分别表示他们 reflect.Value 和 reflect.Type ,并提供两个函数来获
取任意对象的 Value 和 Type,看如下示例:
package main import ( "fmt" "reflect" ) // 定义一个 User 结构体 type User struct { Name string Age int } func main() { u := User{"张三",25} t := reflect.TypeOf(u) fmt.Println(t) } ------------------------------- 输出结果: main.User
reflect.TypeOf 可以获取任意对象的具体类型,通过上面的例子可以看到打印输出的结果是 main.User 这个结构体类型
那么如何反射获取一个对象的 Value:
v := reflect.ValueOf(u) fmt.Println(v) -------------------------- 输出结果: {张三 25}
对于以上两种输出,Go 语言还通过 fmt.Printf 函数提供了更为简便的方法
fmt.Printf("%T ",u) fmt.Printf("%v ",u)
2.reflect.Value 转原始类型
上面的例子中我们通过 reflect.ValueOf 函数把任意类型对象转换成 reflect.Value 类型,如果我们想逆向转换回来呢?其实也
是可以的,reflect.Value 为我们提供了 interface 方法,如下示例:
package main import ( "fmt" "reflect" ) // 定义一个 User 结构体 type User struct { Name string Age int } func main() { u := User{"张三",25} // 使用 refelct.ValueOf 转换成 refelct.Value v := reflect.ValueOf(u) fmt.Println(reflect.TypeOf(v)) // 使用 interface 方法转换成 main.User u1 := v.Interface().(User) fmt.Println(reflect.TypeOf(u1)) } ----------------------------------------------------- 输出结果: reflect.Value main.User
3.获取类型底层类型
底层类型是什么意思?其实对应的主要是基础类型、接口、结构体、指针这些,因为我们可以通过 Type 关键字声明
很多新的类型,比如上面的例子,对象 u 的实际类型是 User,但对应的底层类型是 struct 这个结构体类型,如下示例:
package main import ( "fmt" "reflect" ) // 定义一个 User 结构体 type User struct { Name string Age int } func main() { u := User{"张三",25} t := reflect.TypeOf(u) fmt.Println(t.Kind()) } -------------------------------- 输出结果: struct
4.遍历字段和方法
通过反射,我们可以获取一个结构体类型的字段,也可以获取一个类型的导出方法,这样我们就可以在运行时了解一个
类型的结构,如下示例:
package main import ( "fmt" "reflect" ) // 定义一个 User 结构体 type User struct { Name string Age int } // 给结构体 User 绑定一个方法 func (u User) Print() { fmt.Println("I am User") } func main() { u := User{"张三",26} t := reflect.TypeOf(u) // 获取结构体类型的字段 for i := 0; i < t.NumField(); i++ { fmt.Println(t.Field(i).Name) } // 获取结构体类型的方法 for i := 0; i < t.NumMethod(); i++ { fmt.Println(t.Method(i).Name) } } --------------------------------------------------- 输出结果: Name Age Print
5.修改字段的值
我们还可以通过反射修改某个字段的值,如下示例(使用反射修改变量的值):
package main import ( "fmt" "reflect" ) func main() { x := 2 v := reflect.ValueOf(&x) v.Elem().SetInt(100) fmt.Println(x) } --------------------------------- 输出结果: 100
因为 reflect.ValueOf 返回的是一份值的拷贝,所以要想修改一个变量的值前提是要传入修改变量的地址。
其次需要我们调用 Elem 方法找到指针指向的值。最后我们就可以使用 SetInt 方法修改值了
如何修改结构体字段的值呢?如下示例:
package main import ( "fmt" "reflect" ) // 定义一个结构体 type User struct { Name string Age int } func main() { u := User{"tom",11} v := reflect.ValueOf(&u.Name) v.Elem().SetString("paul") fmt.Println(u) } ---------------------------------------- 输出结果: {paul 11}
6.动态调用方法
结构体的方法不仅可以正常调用,还可以通过反射调用。要想通过反射调用,我们先要获取到需要调用的方法,
然后进行传参调用,如下示例:
package main import ( "fmt" "reflect" ) // 定义一个结构体 type User struct { Name string Age int } // 给 User 绑定一个方法 func (u User) Print(prfix string) { fmt.Printf("%s:Name is %s,Age is %d",prfix,u.Name,u.Age) } func main() { u := User{"lotus",26} v := reflect.ValueOf(u) // MethodByName 根据方法名获取方法对象 mPrint := v.MethodByName("Print") // 构建参数 args := []reflect.Value{reflect.ValueOf("前缀")} fmt.Println(mPrint.Call(args)) } -------------------------------------------------------------- 输出结果: 前缀:Name is lotus,Age is 26[]
MethodByName 可以根据一个方法名获取一个方法对象,然后我们构建好该方法需要的参数,最后调用
Call 就达到了动态调用方法的目的
7 通过反射获取 struct 的 Tag
字段的 Tag 是标记到字段上的,所以我们可以通过先获取字段,然后再获取标记在字段上的 Tag,如下示例:
package main import ( "fmt" "reflect" ) // 定义一个 User 结构体 type User struct { Name string `name` Age int `age` } func main() { var u User t := reflect.TypeOf(u) for i := 0; i < t.NumField(); i++ { sf := t.Field(i) fmt.Println(sf.Tag) } } --------------------------------------------- 输出结果: name age
通过 .Tag 就可以获取到对应的 Tag
8 字段 Tag 的键值对
一个 struct 的字段可能对应多个不同的 Tag,以便满足不同的功能场景,在 Go 中,struct 为我们提供了
键值对的 Tag ,来满足我们针对不同场景的需求,如下示例:
package main import ( "fmt" "reflect" ) // 定义一个结构体 type User struct { Name string `json:"name"` Age int `json:"age"` } func main() { var u User t := reflect.TypeOf(u) for i := 0; i < t.NumField(); i++ { sf := t.Field(i) fmt.Println(sf.Tag.Get("json")) } } -------------------------------------------------- 输出结果: name age
上面例子,使用了键值对的方式配置 struct Tag,Key-Value 以冒号分开,这里的 Key 就是 json,所以,我们
可以通过这个 Key 获取对应的值,也就是通过 .Tag.Get("json") 方法。 Get 方法就是通过一个 Key 获取对应的 tag 设置
除此之外,我们还可以通过设置多个 key,如下示例:
package main import ( "fmt" "reflect" ) // 定义一个结构体 type User struct { Name string `json:"name" bson:"b_name"` Age int `json:"age" bson:"b_age"` } func main() { var u User t := reflect.TypeOf(u) for i := 0; i < t.NumField(); i++ { sf := t.Field(i) fmt.Println(sf.Tag.Get("json"),",",sf.Tag.Get("bson")) } } -------------------------------------------------------------------------- 输出结果: name , b_name age , b_age
多个 Key 之间使用空格分开,然后通过 Get 方法获取不同的 Key 值