GO 语言之反射 Reflect
(给Go开发大全
加星标)
来源:Blockchain Explorer
https://blog.csdn.net/TDCQZD/article/details/82683678
【导读】golang开发中对reflect的使用有多种方式,看看reflect有什么接口、有助于扩展思路。
一、从案列场景引入反射
定义了两个函数test1和test2,定义一个适配器函数用作统一处理接口:
(1) 定义了两个函数
test1 := func(v1 int, v2 int) {
t.Log(v1, v2)
}
test2 := func(v1 int, v2 int, s string) {
t.Log(v1, v2, s)
}
(2) 定义一个适配器函数用作统一处理接口, 其大致结构如下:
bridge := func(call interface{}, args ...interface{}) {
//内容
}
//实现调用test1对应的函数
bridge(test1, 1, 2)
//实现调用test2对应的函数
bridge(test2, 1, 2, "test2")
(3) 要求使用反射机制完成
package test
import (
"testing"
"reflect"
)
/*反射的最佳实践*/
/*
3) 定义了两个函数test1和test2,定义一个适配器函数用作统一处理接口【了解】:
(1) 定义了两个函数
test1 := func(v1 int, v2 int) {
t.Log(v1, v2)
}
test2 := func(v1 int, v2 int, s string) {
t.Log(v1, v2, s)
}
(2) 定义一个适配器函数用作统一处理接口, 其大致结构如下:
bridge := func(call interface{}, args ...interface{}) {
//内容
}
//实现调用test1对应的函数
bridge(test1, 1, 2)
//实现调用test2对应的函数
bridge(test2, 1, 2, "test2")
(3) 要求使用反射机制完成
*/
func TestReflectFunc(t *testing.T) {
call1 := func(v1 int, v2 int) {
t.Log(v1, v2)
}
call2 := func(v1 int, v2 int, s string) {
t.Log(v1, v2, s)
}
var (
function reflect.Value
inValue []reflect.Value
n int
)
bridge := func(call interface{}, args ...interface{}) {
n = len(args)
inValue = make([]reflect.Value, n)
for i := 0; i < n; i++ {
inValue[i] = reflect.ValueOf(args[i])
}
function = reflect.ValueOf(call)
function.Call(inValue)
}
bridge(call1, 1, 2)
bridge(call2, 1, 2, "test2")
}
二、反射的基本介绍
1、反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type),类别(kind)
2、 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
3、通过反射,可以修改变量的值,可以调用关联的方法。
4、使用反射,需要 import (“reflect”)
5、 示意图
三、反射的应用场景
1、反射常见应用场景有以下两种
不知道接口调用哪个函数,根据传入参数在运行时确定调用的具体接口,这种需要对函数或方法反射。例如以下这种桥接模式, 比如我前面提出问题。
第一个参数funcPtr以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数
2、对结构体序列化时,如果结构体有指定Tag, 也会使用到反射生成对应的字符串。
四、反射重要的函数和概念
1、reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型
2、reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型reflect.Value 是一个结构体类型。【看文档】, 通过reflect.Value,可以获取到关于该变量的很多信息
3、变量、interface{} 和 reflect.Value是可以相互转换的,这点在实际开发中,会经常使用到。
变量 interface{} 和reflect.Value()是可以相互转换的,
变量 interface{} 和valueOf()之间的转换
五、快速入门的案例
反射普通变量和结构体
//反射操作方法-基本数据类型float64
func reflectOper(b interface{}) {
//通过反射获取的传入的变量的 type , kind, 值
//1. 先获取到 reflect.Type
rTye := reflect.TypeOf(b)
fmt.Println("rtype=",rTye)
rKind := rTye.Kind()
fmt.Println("rKind=",rKind)
//2. 获取到 reflect.Value
rVal := reflect.ValueOf(b)
fmt.Println("rVal=",rVal)
// 将 reflect.Value 转成 interface{}
iV := rVal.Interface()
fmt.Println("iV=",iV)
//将 interface{} 通过断言转成需要的类型
num := iV.(float64)
fmt.Println("num=",num)
}
type Student struct{
Name string
Age int
}
//反射操作方法-结构体
func reflectOPerStruct(b interface{}) {
//通过反射获取的传入的变量的 type , kind, 值
//1. 先获取到 reflect.Type
rTyp := reflect.TypeOf(b)
fmt.Println("rType=", rTyp)
//2. 获取到 reflect.Value
rVal := reflect.ValueOf(b)
fmt.Println("rVal=", rVal)
//下面我们将 rVal 转成 interface{}
iV := rVal.Interface()
fmt.Printf("iv=%v iv type=%T \n", iV, iV)
//将 interface{} 通过断言转成需要的类型
stu, ok := iV.(Student)
if ok {
fmt.Printf("stu.Name=%v\n", stu.Name)
}
}
func RefelctDemo(){
// var num float64 = 12.0
// reflectOper(num)//对普通变量进行反射
stu :=Student{
Name : "张三",
Age : 30,
}
reflectOPerStruct(stu)//对结构体进行反射
}
测试结果:
e:\GoProject\src\go_code\grammar\reflect\code\main>go run main.go
rType= utils.Student
rVal= {张三 30}
iv={张三 30} iv type=utils.Student
stu.Name=张三
e:\GoProject\src\go_code\grammar\reflect\code\main>
六、 使用细节和注意事项
1、reflect.Value.Kind,获取变量的类别,返回的是一个常量
2、Type 和 Kind 的区别
Type是类型, Kind是类别, Type 和 Kind 可能是相同的,也可能是不同的.
比如: var num int = 10 num的Type是int , Kind也是int
比如: var stu Student stu的Type是 pkg1.Student , Kind是struct
3、通过反射可以在让变量在 interface{} 和Reflect.Value 之间相互转换,
4、使用反射的方式来获取变量的值(并返回对应的类型),要求数据类型匹配,比如x是int, 那么就应该使用 reflect.Value(x).Int(), 而不能使用其它的,否则报panic。
5、通过反射的来修改变量, 注意当使用SetXxx方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到reflect.Value.Elem()方法
七、反射最佳实践
1、使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
import (
"reflect"
"fmt"
)
/*反射的最佳实践*/
// 1、使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
/*
分析:
1、结构体 有四个字段 Name Age Score Sex
2、声明结构体方法 GetSum Set Print()
3、TestStruct 处理结构体方法和字段
4、main方法调用
*/
type Monster struct{
Name string `json:"name"`
Age int `json:"age"`
Score float64 `json:"score"`
Sex string
}
func (m Monster)Print() {
fmt.Println("---start~----")
fmt.Println(m)
fmt.Println("---end~----")
}
func (m Monster)GetSum(n1, n2 int) int {
return n1 + n2
}
func (m Monster)Set(name string, age int, score float64, sex string) Monster {
m.Name = name
m.Age = age
m.Score = score
m.Sex = sex
return m
}
func StructOpera(i interface{}) {
//获取reflect.Type 类型
rType := reflect.TypeOf(i)
//获取reflect.Value 类型
rValue := reflect.ValueOf(i)
//获取到a对应的类别
rKind := rValue.Kind()
//如果传入的不是struct,就退出
if rKind != reflect.Struct {
fmt.Println("expect struct")
return
}
//获取到该结构体有几个字段
numField := rValue.NumField()
//变量结构体的所有字段
for i := 0; i < numField; i++ {
fmt.Printf("Field %d: 值为=%v\n", i, rValue.Field(i))
//获取到struct标签, 注意需要通过reflect.Type来获取tag标签的值
tagVal := rType.Field(i).Tag.Get("json")
//如果该字段是tag标签就显示,否则就不显示
if tagVal !="" {
fmt.Printf("Field %d: tag为=%v\n", i, tagVal)
}
}
//获取到该结构体有多少个方法
numMethod := rValue.NumMethod()
fmt.Printf("struct has %d methods\n", numMethod)
//方法的排序默认是按照 函数名的排序(ASCII码)
rValue.Method(1).Call(nil) //获取到第二个方法。调用它
//调用结构体的第1个方法Method(0)
//声明了 []reflect.Value
var params []reflect.Value
params = append(params,reflect.ValueOf(10))
params = append(params,reflect.ValueOf(20))
res := rValue.Method(0).Call(params)//传入的参数是 []reflect.Value, 返回[]reflect.Value
fmt.Println("res=", res[0].Int()) //返回结果, 返回的结果是 []reflect.Value*/
var params1 []reflect.Value
params1 = append(params1,reflect.ValueOf("狐狸精"))
params1 = append(params1,reflect.ValueOf(500))
params1 = append(params1,reflect.ValueOf(60.0))
params1 = append(params1,reflect.ValueOf("女"))
res1 := rValue.Method(2).Call(params1)//传入的参数是 []reflect.Value, 返回[]reflect.Value
fmt.Println("res=", res1[0])
}
func StructOperaDemo() {
//创建了一个Monster实例
var m Monster = Monster{
Name: "黄鼠狼精",
Age: 400,
Score: 30.8,
}
//将Monster实例传递给StructOpera函数
StructOpera(m)
fmt.Println("m=",m)
}
测试结果:
Field 0: 值为=黄鼠狼精
Field 0: tag为=name
Field 1: 值为=400
Field 1: tag为=age
Field 2: 值为=30.8
Field 2: tag为=score
Field 3: 值为=
struct has 3 methods
---start~----
{黄鼠狼精 400 30.8 }
---end~----
res= 30
res= {狐狸精 500 60 女}
m= {黄鼠狼精 400 30.8 }
2、使用反射的方式来获取结构体的tag标签, 遍历字段的值,修改字段值,调用结构体方法(要求:通过传递地址的方式完成, 在前面案例上修改即可)
package test
import (
"encoding/json"
"fmt"
"reflect"
)
/*反射的最佳实践*/
// 3、使用反射的方式来获取结构体的tag标签, 遍历字段的值,修改字段值,调用结构体方法
// (要求:通过传递地址的方式完成, 在前面案例上修改即可)
type Monster struct {
Name string `json:"monster_name"`
Age int
Score float32
Sex string
}
func (s Monster) Print() {
fmt.Println("---start----")
fmt.Println(s)
fmt.Println("---end----")
}
func TestStruct(a interface{}) {
tye := reflect.TypeOf(a)
val := reflect.ValueOf(a)
kd := val.Kind()
if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct {
fmt.Println("expect struct")
return
}
num := val.Elem().NumField()
val.Elem().Field(0).SetString("白象精")
for i := 0; i < num; i++ {
fmt.Printf("%d %v\n", i, val.Elem().Field(i).Kind())
}
fmt.Printf("struct has %d fields\n", num)
tag := tye.Elem().Field(0).Tag.Get("json")
fmt.Printf("tag=%s\n", tag)
numOfMethod := val.Elem().NumMethod()
fmt.Printf("struct has %d methods\n", numOfMethod)
val.Elem().Method(0).Call(nil)
}
func StructOperaDemo2() {
var a Monster = Monster{
Name: "黄狮子",
Age: 408,
Score: 92.8,
}
//先说明一下,Marshal就是通过反射获取到struct的tag值的...
result, _ := json.Marshal(a)
fmt.Println("json result:", string(result))
TestStruct(&a)
fmt.Println(a)
}
测试结果:
json result: {"monster_name":"黄狮子","Age":408,"Score":92.8,"Sex":""}
0 string
1 int
2 float32
3 string
struct has 4 fields
tag=monster_name
struct has 1 methods
---start----
{白象精 408 92.8 }
---end----
{白象精 408 92.8 }
3、使用反射操作任意结构体类型:【了解】
package test
import (
"reflect"
"testing"
)
/*反射的最佳实践*/
// 5、使用反射操作任意结构体类型:【了解】
type user struct {
UserId string
Name string
}
func TestReflectStruct(t *testing.T) {
var (
model *user
sv reflect.Value
)
model = &user{}
sv = reflect.ValueOf(model)
t.Log("reflect.ValueOf", sv.Kind().String())
sv = sv.Elem()
t.Log("reflect.ValueOf.Elem", sv.Kind().String())
sv.FieldByName("UserId").SetString("12345678")
sv.FieldByName("Name").SetString("nickname")
t.Log("model", model)
}
4、使用反射创建并操作结构体
package test
import (
"testing"
"reflect"
)
/*反射的最佳实践*/
// 6、使用反射创建并操作结构体
type User struct {
UserId string
Name string
}
func TestReflectStructPtr(t *testing.T) {
var (
model *User
st reflect.Type
elem reflect.Value
)
st = reflect.TypeOf(model) //获取类型 *user
t.Log("reflect.TypeOf", st.Kind().String()) // ptr
st = st.Elem() //st指向的类型
t.Log("reflect.TypeOf.Elem", st.Kind().String()) //struct
elem = reflect.New(st) //New返回一个Value类型值,该值持有一个指向类型为typ的新申请的零值的指针
t.Log("reflect.New", elem.Kind().String()) // ptr
t.Log("reflect.New.Elem", elem.Elem().Kind().String()) //struct
//model就是创建的user结构体变量(实例)
model = elem.Interface().(*User) //model 是 *user 它的指向和elem是一样的.
elem = elem.Elem() //取得elem指向的值
elem.FieldByName("UserId").SetString("12345678") //赋值..
elem.FieldByName("Name").SetString("nickname")
t.Log("model model.Name", model, model.Name)
}
- EOF -
Go 开发大全
参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。
关注后获取
回复 Go 获取6万star的Go资源库
分享、点赞和在看
支持我们分享更多好文章,谢谢!