这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
1. 什么是反射
- 反射是指程序运行的时候可以检测自身状态或者行为的一种能力
- 编译期间可以不知道变量的类型,我们可以将变量通过接口传递给函数,然后通过接口调用方法,但是编译期间是不知道具体的方法是什么以及需要什么类型
- Go语言中有一个reflect反射包,里面有reflect.Type接口和reflect.Value结构体,他们都只接受接口类型的参数,里面提供了很多方法帮助我们获取接口中的方法或者是属性
2. 反射三大定律
1. 接口类型的变量转换成反射类型的对象
- 使用TypeOf()方法和ValueOf()方法都可以将接口类型的变量转换成反射对象(Type对象或是Value对象)
2. 反射类型的对象转换成接口类型的变量
- Value对象可以调用Interface()方法转换成原变量类型
3. 通过反射类型的对象修改原变量的值
- Go语言中想要通过对反射对象的修改来对原变量进行修改的话必须传递原变量的指针,不然的话反射对象就只是原对象的一个副本,对反射对象的修改不会影响到原变量
- 对象可不可以修改,可以通过CanSet()方法进行判断
- 所以要想原对象可以修改需要满足以下两个条件:
-
- 传递原变量的指针类型
- 使用Elem()方法返回指针指向的数据
3. reflect.Type和reflect.Value
- reflect包中的Type是一个接口类型,里面定义了一系列的方法,有一系列实现接口的结构体在type.go文件中定义;Vlaue是一个结构体类型,里面定义了很多字段,同时给结构体定义了很多方法,通过函数接收者指定
- Go语言中两个重要的方法:TypeOf()和ValueOf(),这两个方法都只接收接口类型的参数
4. 反射最佳实践
package main
import (
"fmt"
"reflect"
)
type User struct {
UserName string `json:"username"`
Age int `json:"age"`
}
func (u User) GetAge(age int, num int) int {
lastAge := age + num
return lastAge
}
func TestRefStru(v interface{}) {
// 对结构体变量进行反射
// 获取reflect.Type类型
rType := reflect.TypeOf(v)
// 获取reflect.Value类型
rVal := reflect.ValueOf(v)
// 获取v对应的类别
kd := rVal.Kind()
// 如果传入的非struct就退出
if kd != reflect.Struct {
return
}
// 获取结构体中有多少个字段
FieldNum := rVal.NumField()
// 对字段的tag进行反射,获取tag标签值
for i := 0; i < FieldNum; i++ {
// 获取struct标签,通过reflect.Type来获取tag标签值
tagVal := rType.Field(i).Tag.Get("json")
// 如果字段存在就打印出来
if tagVal != "" {
fmt.Printf("tag:%v \n", tagVal)
}
}
// 获取结构体中有多少个方法
MethodNum := rVal.NumMethod()
fmt.Println("方法个数为:", MethodNum)
// 方法排序是按照ASCII排序,与在结构体中的顺序无关
// 该方法无参数的调用
rVal.Method(0).Call([]reflect.Value{reflect.ValueOf(1), reflect.ValueOf(2)}) //调用第一个方法
// 含参数的调用,注意Call方法传入的的[]reflect.Value类型的参数,返回值是[]reflect.Value类型
var parames []reflect.Value
parames = append(parames, reflect.ValueOf(20))
parames = append(parames, reflect.ValueOf(5))
res := rVal.Method(0).Call(parames)
fmt.Println("年龄", res[0].Int())
}
func main() {
// 创建一个User实例,test
u := User{
UserName: "lily",
Age: 20,
}
TestRefStru(u)
}