反射的应用场景:结构体的tag(标签)底层用到的就是反射;编写函数的适配器 ,桥连接(自己设计go的框架);
基本介绍
1.反射可以在运行时动态获取变量的各种信息,比如变量的类型(type),类别(kind);
2.如果是结构体变量(实例),还可以获取到结构体本身的信息(包括结构体的字段,方法);
3.通过反射,可以修改变量的值,可以调用关联的方法。
4.使用反射,需要import( "reflect" )
反射重要的函数和概念
1.reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type 类型
2.reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型,reflect.Value 是一个结构体类型,通过reflect.Value,可以获取到关于该变量的很多信息。
3.变量,interface{},和reflect.Value是可以相互转换的,这点在实际开发中,经常会用到。
通常的操作方式:
package main
import ( "reflect" "fmt" )
// 演示反射(基本类型)
func reflectTest01(t interface{}) {
// 通过反射获取的传入的变量 type,kind, 值
// 1.先获取到 reflect.Type
rType := reflect.TypeOf(t)
fmt.Println("rType==", rType) // rType== int
// 2.获取到 reflect.Value
rVal := reflect.ValueOf(t)
n2 := 2 + rVal.Int()
fmt.Println("n2--", n2)
fmt.Printf("rVal==%v rVal type==%T \n", rVal, rVal) // rVal==100 rVal type==reflect.Value
// 3.将rVal 转成 interface{} 这里转成interface就是原来的类型了,为啥需要用断言呢?因为编译器无法通过
iV := rVal.Interface()
// 4.将 iV 通过断言转成原来或者需要的类型
num2 := iV.(int)
fmt.Printf("num2===%v num2 type ==%T \n", num2, num2) // num2===100 num2 type ==int
}
func main() {
// 演示对(基本数据类型,interface{}, reflect.Value)进行反射的基本操作
var num int = 100
reflectTest01(num)
}
反射注意事项和细节说明
1.reflect.Value.Kind 获取变量的类别(类别范围大于类型,电器(冰箱,洗衣机等),电器就是类别,里面包含的就是类型),返回的是一个常量。
2.Type是类型,Kind是类别,Type 和 Kind 可能是相同的,也可能是不同的;
比如:var num int = 20 num的Type是int,Kind也是int(基本数据类型两者是一样的);
var stu Student stu的Type是 包名.Student,Kind是struct;
变量在interface{} 和 reflect.Value 之间相互转换;
并返回对应的类型),要求数据类型匹配,比如x是int,那么就应该使用reflect.Value(x).Int(),而不是使用其他的,否则报panic;
5.通过反射来修改变量,注意当使用SetXx方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量的值,同时需要使用到reflect.Value.Elem() 方法;
实际案例:
var num1 int = 20
reflectTest01(&num1)
fmt.Println("num1---", num1) // num1--- 30
reflectTest01里的代码:
// 获取到 reflect.Value
rVal := reflect.ValueOf(t)
// Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装
rVal.Elem().SetInt(30) // 即完成修改
// 更好的理解 rVal.Elem()
num3 := 40
ptr *int = &num3
num4 := *ptr // 等价于 rVal.Elem()
反射的最佳实践:
1.使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
实际案例:
package main
import ( "fmt" "reflect" )
// 定义一个结构体
type Monster struct {
Name string `json:"name"`
Age int `json:"monster_age"`
Score float64
Sex string
}
// 方法,显示Monster实例的值
func (m Monster) Print() {
fmt.Println("----start----")
fmt.Println(m)
fmt.Println("----end----")
}
// 方法,返回两个数的和
func (m Monster) GetSum(n1, n2 int) int {
return n1 + n2
}
// 方法,接收四个值,给Monster实例赋值
func (m Monster) SetVal(name string, age int, score float64, sex string) {
m.Name = name
m.Age = age
m.Score = score
m.Sex = sex
}
// 方法 结构体测试
func TestStruct(t interface{}) {
// 获取reflect.Type 类型
rType := reflect.TypeOf(t)
// 获取reflect.Value 类型
rVal := reflect.ValueOf(t)
// 获取到t对应的类别
kd := rVal.Kind()
// 如果传入的不是struct,就退出
if kd != reflect.Ptr && rVal.Elem().Kind() != reflect.Struct {
fmt.Println("expect struct ptr")
return
}
// 获取到t结构体有几个字段
f_num := rVal.Elem().NumField()
fmt.Printf("struct has %d fields \n", f_num)
// 遍历结构体的所有字段
for i:=0; i < f_num; i ++ {
// 获取struct每个字段的值,但是此时获取的不能做任何操作,如果想操作可以转
fmt.Printf("Field %d : 值为=%v \n", i, rVal.Elem().Field(i))
// 获取到struct标签,注意需要通过reflect.Type 来获取tag标签的值
tagVal := rType.Elem().Field(i).Tag.Get("json")
if tagVal != "" { // 有些字段没有标签
fmt.Printf("Field %d: tag为=%v \n", i, tagVal)
}
}
// 获取到结构体有多少个方法
numOfMethod := rVal.Elem().NumMethod()
fmt.Printf("struct has %d methods \n", numOfMethod)
// var params []reflect.Value
rVal.Elem().Method(1).Call(nil) // 获取到第二个方法且调用(但排序实际是按函数名字母的ASCII码)
// 调用结构体的第一个方法Method(0)
var params []reflect.Value // 定义了 []reflect.Value
params = append(params, reflect.ValueOf(10))
params = append(params, reflect.ValueOf(60))
res_slice := rVal.Elem().Method(0).Call(params) // 传入的参数是 []reflect.Value, 返回的结果类型也是[]reflect.Value
fmt.Printf("res_slice=%v type ==%T \n", res_slice[0].Int(), res_slice[0].Int()) // 返回结果是 []reflect.Value
// 修改结构体字段值 需要传 地址
rVal.Elem().Field(0).SetString("沙和尚")
rVal.Elem().Method(1).Call(nil)
}
func main() {
// 使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值
m := Monster{
Name : "孙悟空",
Age : 500,
Score : 99.9,
Sex : "男",
}
// TestStruct(m) // 只是读
TestStruct(&m) // 传地址可以修改字段值
fmt.Println(m)
}