反射
先看一个问题
- 前面我们学习过结构体的序列化和反序列化,里面 tags 的底层采用的就是反射的原理。 ```go package main
 
import ( “encoding/json” “fmt” )
type Monster struct {
    Name string json:"name"
    Age int json:"age"
    Sal float64 json:"sal"
    Sex string json:"sex"
}
func main() { monster := Monster{ Name: “玉兔精”, Age: 20, Sal: 888.99, Sex: “female”, }
data, _ := json.Marshal(monster)fmt.Println("json result: ", string(data))
}
2. 应用领域```go// 定义了两个匿名函数test1 := func(v1 int, v2 int) {t.Log(v1, v2)}test2 := func(v1 int, v2 int, s string) {t.Log(v1, v2, s)}// 定义一个适配器函数用作统一处理接口,其大致结构如下:bridge := func(call interface{}, args...interface{}) {//内容}//实现调用 test1 对应的函数bridge(test1, 1, 2)//实现调用 test2 对应的函数bridge(test2, 1, 2, "test2")// 要求使用反射机制完成
基本介绍
- 反射的基本介绍 
- 反射可以在运行时动态获取变量的各种信息,比如变量的类型,类别
 - 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
 - 通过反射,可以修改变量的值,可以调用关联的方法。
 - 使用反射,需要 import(“reflect”)
 
 - 反射重要的函数和概念 
- reflect.TypeOf(变量名), 获取变量的类型,返回 reflect.Type 类型
 - reflect.ValueOf(变量名),获取变量的值,返回 reflect.Value 类型 reflect.Value 是一个结构体类型。
 - 变量、interface{} 和 reflect.Value 是可以相互转换的。
 
 类型转换
编写一个案例。演示对基本数据类型、interface{}、reflect.Value进行反射操作。 ```go package main
import ( “fmt” “reflect” )
//专门演示反射 func reflectTest01(b interface{}) {
//通过反射获取传入的变量的 type,kind,value//1. 先获取到 reflect.Typertyp := reflect.TypeOf(b)fmt.Println("rtype =", rtyp)//2. 获取到 reflect.ValuerVal := reflect.ValueOf(b)fmt.Println("rVal =", rVal) //不是普通的100fmt.Printf("rVal = %v rVal = %T \n", rVal, rVal)//将 reflect.Value 改为 intn2 := 2 + rVal.Int()fmt.Println(n2)// 将 reflect.Value 改为 interface{}iVal := rVal.Interface()//将 interface{} 通过断言转成需要的类型num2 := iVal.(int)fmt.Printf("num2 = %v num2 = %T \n", num2, num2)
}
func main() { / 案例一: 编写一个案例。演示对基本数据类型、interface{}、reflect.Value进行反射操作。 /
//1. 先定义一个 intvar num int = 100reflectTest01(num)
}
2. 编写一个案例,演示对(结构体类型、interface{}、reflect.Value)进行反射的基本操作。```gopackage mainimport ("fmt""reflect")type Student struct {Name stringAge int}//专门演示反射[对结构体的反射]func reflectTest02(b interface{}) {//通过反射获取传入的变量的 type,kind,value//1. 先获取到 reflect.TyperType := reflect.TypeOf(b)fmt.Println("rType =", rType)//2. 获取到 reflect.ValuerVal := reflect.ValueOf(b)fmt.Println("rVal =", rVal)fmt.Printf("rVal = %v rVal = %T \n", rVal, rVal)// 将 reflect.Value 改为 interface{}iVal := rVal.Interface()fmt.Printf("iVal = %v iVal = %T \n", iVal, iVal)//将 interface{} 通过断言转成需要的类型//println(iVal.Name) 报错//这里,我们就简单使用了待检测的类型//可以使用 switch 的断言形式来做得更加灵活stu, ok := iVal.(Student)if ok {fmt.Printf("stu.Name = %v \n", stu.Name)}}func main() {//定义一个 Student 实例stu := Student{Name: "Tom",Age: 20,}reflectTest02(stu)}
反射注意事项和细节说明
- reflect.Value.Kind,获取变量的类别,返回的是一个常量。
 - Type 是类型,kind 是类型,Type 和 kind 可能是相同的,也可能是不同的。 
- 比如:var num int = 10 num 的 Type 是 int,Kind 也是 int。
 - 比如:var stu Student stu 的 Type 是包名.student,Kind 是 struct
 
 - 通过反射可以让变量在 interface{} 和 reflect.Value 之间相互转换。
 - 使用反射的方式来去变量的值(并返回对应的类型),要求数据类型匹配,比如 x 是 int,那么就应该使用 reflect.Value(x).Int(),而不能使用其他的,否则报 panic。
 - 通过反射的来修改变量,注意当使用 SetXxx 方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量的值,同时需要使用到 reflect.Value.Elem() 方法。 ```go package main
 
import ( “fmt” “reflect” )
//通过反射,修改 num int 的值;修改 Student 的值
func reflect01(b interface{}) { //获取到 reflect.Value rVal := reflect.ValueOf(b) // 看看 rVal 的 kind fmt.Printf(“rVal Kind = %v \n”, rVal.Kind())
// rVal.SetInt(20) errorrVal.Elem().SetInt(20)
}
func main() { var num int = 10 reflect01(&num) fmt.Println(num) }
<a name="314422fe"></a>## 反射的最佳实践1. 使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值。```gopackage mainimport ("fmt""reflect")// 定义了一个 Monster 结构体type Monster struct {Name string `json:"name"`Age int `json:"age"`Score float32Sex string}//方法,显示 s 的值func(s Monster) Print() {fmt.Println("---start---")fmt.Println(s)fmt.Println("---end---")}//方法, 返回两个数的和func (s Monster) GetSum(n1 int, n2 int) int {return n1 + n2}//方法,接受四个值,给 s 赋值func (s Monster) Set(name string, age int, score float32, sex string) {s.Name = names.Age = ages.Score = scores.Sex = sex}func TestStruct(a interface{}) {// 获取 reflect.Type 类型typ := reflect.TypeOf(a)// 获取 reflect.Valueval := reflect.ValueOf(a)// 获取到 a 对应的类别kd := val.Kind()// 如果传入的不是 struct ,就退出函数if kd != reflect.Struct {fmt.Println("expect struct...")return}// 获取该结构体有几个字段num := val.NumField()fmt.Printf("struct has %d fields\n", num)//遍历结构体的所有字段for i := 0; i < num; i++ {fmt.Printf("Field%d: %v \n", i, val.Field(i))// 获取到 struct 标签,注意需要通过 reflect.type 来获取 tag 标签的值tagVal := typ.Field(i).Tag.Get("json")// 如果该字段有 tag 标签就显示,否则不显示if tagVal != "" {fmt.Printf("Field%d: tag = %v \n", i, tagVal)}}// 获取到当前结构体有多少个方法numOfMethod := val.NumMethod()fmt.Printf("struct has %d methods \n", numOfMethod)// var params []reflect.Value// 方法的排序默认是按照 函数名的排序(ASCII码)val.Method(1).Call(nil) // 获取到第二个方法,调用它// 调用结构体的第1个方法 Method(1)var params []reflect.Value // 声明 []reflect.Value 切片params = append(params, reflect.ValueOf(10))params = append(params, reflect.ValueOf(40))res := val.Method(0).Call(params) // 传入的参数是 []reflect.Value , 返回[]reflect.Valuefmt.Println("res =", res[0].Int())}func main() {//创建了一个 Monster 实例monster := Monster{Name: "黄鼠狼精",Age: 400,Score: 30.8,}// 将 Monster 实例传递给 TestStruct 函数TestStruct(monster)}
- 使用反射的方式来获取结构体的 tag 标签,遍历字段的值,修改字段值,调用结构体方法。 ```go package main
 
import ( “encoding/json” “fmt” “reflect” )
type Monster struct {
    Name string json:"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 main() { monster := Monster{ Name: “黄狮子”, Age: 408, Score: 92.8, }
//先说明一下, Marshal 就是通过反射获取到 struct 的 tag 值result, _ := json.Marshal(monster)fmt.Println("json result:", string(result))TestStruct(&monster)fmt.Println(monster)
}
3. 定义了两个函数 test1 和 test2,定义一个适配器函数用作统一处理接口。- 定义了两个函数- 定义一个适配器函数用作统一处理接口- 要求使用反射机制完成```gopackage testimport ("reflect""testing")func TestReflectFunction(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.ValueinValue []reflect.Valuen 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")}
- 使用反射操作任意结构体类型 ```go package test
 
import ( “reflect” “testing” )
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.Value.Elem", sv.Kind().String())t.Log("sv.Elem().Kind()", sv.Elem().Kind())sv = sv.Elem()sv.FieldByName("UserId").SetString("12345678")sv.FieldByName("Name").SetString("nickname")t.Log("model", model)
}
5. 使用反射创建并操作结构体```gopackage testimport ("reflect""testing")type user struct {UserId stringName string}func TestReflectStructPtr(t *testing.T) {var (model *userst reflect.Typeelem reflect.Value)st = reflect.TypeOf(model) //获取类型 *usert.Log("reflect.TypeOf", st.Kind().String()) //ptrst = st.Elem() // st 指向的类型t.Log("reflect.TypeOf.elem", st.Kind().String()) //structelem = reflect.New(st) // New 返回一个 value 类型值,该值持有一个指针t.Log("reflect.New", elem.Kind().String()) // ptrt.Log("reflect.New.Elem", elem.Elem().Kind().String()) //struct// model 就是创建的 user 结构体变量(实例)model = elem.Interface().(*user) // model 是 *user 它的指向和 elem 是一样的t.Log("model model.Name", model, model.Name)t.Log("elem elem.Elem", elem, elem.Elem())elem = elem.Elem() //取得 elem 指向的值elem.FieldByName("UserId").SetString("12345678") //赋值elem.FieldByName("Name").SetString("nickname")t.Log("model model.Name", model, model.Name)t.Log("*model", *model)}
- 课堂练习 ```go package main
 
import ( “fmt” “reflect” )
/ 课堂练习: (1) 编写一个 Cal 结构体,有两个字段 Num1,Num2 (2) 方法 GetSub(name string) (3) 使用反射机制完成对 GetSub 的调用,输出形式为 “tom 完成了 减法运行,8 - 3 = 5” /
type Cal struct { Num1 int Num2 int }
func (cal Cal) GetSub(name string) { fmt.Printf(“%v 完成了减法运行,%v - %v = %v \n”, name, cal.Num1, cal.Num2, cal.Num1 - cal.Num2) }
func TestStruct(a interface{}) { //typ := reflect.TypeOf(a) val := reflect.ValueOf(a) kd := val.Kind() // fmt.Println(val.Elem().Kind()) // struct if kd != reflect.Ptr && val.Elem().Kind() == reflect.Struct { fmt.Println(“expect struct…”) return }
num := val.Elem().NumField()fmt.Printf("struct has %d fields \n", num)for i := 0; i < num; i++ {fmt.Printf("field(%d) %v \n", i, val.Elem().Field(i))}numOfMethod := val.Elem().NumMethod()fmt.Printf("struct has %d methods \n", numOfMethod)// 调用结构体的第1个方法 Method(1)var params []reflect.Value // 声明 []reflect.Value 切片params = append(params, reflect.ValueOf("tom"))val.Elem().Method(0).Call(params)
}
func main() { cal := Cal{ Num1: 8, Num2: 3, }
cal.GetSub("tom")TestStruct(&cal)
} ```
