反射
先看一个问题
- 前面我们学习过结构体的序列化和反序列化,里面 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.Type
rtyp := reflect.TypeOf(b)
fmt.Println("rtype =", rtyp)
//2. 获取到 reflect.Value
rVal := reflect.ValueOf(b)
fmt.Println("rVal =", rVal) //不是普通的100
fmt.Printf("rVal = %v rVal = %T \n", rVal, rVal)
//将 reflect.Value 改为 int
n2 := 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. 先定义一个 int
var num int = 100
reflectTest01(num)
}
2. 编写一个案例,演示对(结构体类型、interface{}、reflect.Value)进行反射的基本操作。
```go
package main
import (
"fmt"
"reflect"
)
type Student struct {
Name string
Age int
}
//专门演示反射[对结构体的反射]
func reflectTest02(b interface{}) {
//通过反射获取传入的变量的 type,kind,value
//1. 先获取到 reflect.Type
rType := reflect.TypeOf(b)
fmt.Println("rType =", rType)
//2. 获取到 reflect.Value
rVal := 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) error
rVal.Elem().SetInt(20)
}
func main() { var num int = 10 reflect01(&num) fmt.Println(num) }
<a name="314422fe"></a>
## 反射的最佳实践
1. 使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值。
```go
package main
import (
"fmt"
"reflect"
)
// 定义了一个 Monster 结构体
type Monster struct {
Name string `json:"name"`
Age int `json:"age"`
Score float32
Sex 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 = name
s.Age = age
s.Score = score
s.Sex = sex
}
func TestStruct(a interface{}) {
// 获取 reflect.Type 类型
typ := reflect.TypeOf(a)
// 获取 reflect.Value
val := 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.Value
fmt.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,定义一个适配器函数用作统一处理接口。
- 定义了两个函数
- 定义一个适配器函数用作统一处理接口
- 要求使用反射机制完成
```go
package test
import (
"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.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")
}
- 使用反射操作任意结构体类型 ```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. 使用反射创建并操作结构体
```go
package test
import (
"reflect"
"testing"
)
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 类型值,该值持有一个指针
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 是一样的
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)
} ```