欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

反射(reflect)机制

发布时间:2025/3/21 编程问答 32 豆豆
生活随笔 收集整理的这篇文章主要介绍了 反射(reflect)机制 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

什么是反射

官方对此有个非常简明的介绍,两句话耐人寻味:

  • 反射提供一种让程序检查自身结构的能力
  • 反射是困惑的源泉
  • 要深刻理解反射,个人感觉需要花时间在官方博客上再加以练习,循序渐进,慢慢体会。

    反射的三个定律

    反射就是检查interface的(value, type)对的,因为任何类型的变量或方法都是实现了空接口。具体一点说就是Go提供一组方法提取interface的value,提供另一组方法提取interface的type.

    官方提供了三条定律来说明反射,比较清晰,下面也按照这三定律来总结。

    反射包里有两个接口类型要先了解一下.

    • reflect.Type 提供一组接口处理interface的类型,即(value, type)中的type
    • reflect.Value提供一组接口处理interface的值,即(value, type)中的value

    下面会提到反射对象,所谓反射对象即反射包里提供的两种类型的对象。

    • reflect.Type 类型对象
    • reflect.Value类型对象

    1. 反射的第一定律:反射可以将interface类型变量转换成为reflect类型变量

    这里的reflect类型指的是reflect.Type和reflect.Value类型的变量

    package mainimport ("fmt""reflect" )func main() {var x float64 = 8.5t := reflect.TypeOf(x) // 这里的t类型为:reflect.Typefmt.Println("type:", t)v := reflect.ValueOf(x) // 这里的v类型为:reflect.Valuefmt.Println("value:", v) }

    2. 反射第二定律:反射可以将reflect类型对象还原成interface类型对象

    package mainimport ("fmt""reflect" )func main() {var x float64 = 8.5v := reflect.ValueOf(x) //这里v的类型为:reflect.Valuevar y float64 = v.Interface().(float64) //v通过Interface()函数将反射类型转换为interface类型变量,再通过断言为float64获取值fmt.Println("value:", y) }

    3. 反射第三定律:如果要修改reflect类型对象,则value必须是可设置的

    package mainimport ("reflect""fmt" )func main() {var x float64 = 8.5fmt.Println("x :", x)v := reflect.ValueOf(&x)fmt.Println("settability of v:", v.CanSet())//v.SetFloat(5.8) // 这里会报panic的错误,因为这时候v是不可设置的fmt.Println("settability of v:", v.Elem().CanSet())v.Elem().SetFloat(5.8)fmt.Println("x :", v.Elem().Interface().(float64)) }

    上面11行v代表的是指针地址,我们要设置的是指针所指向的内容,也即我们想要修改的是*v。 那怎么通过v修改x的值呢?

    reflect.Value提供了Elem()方法,可以获得指针向指向的value,也就是第14行和第15行的操作。

    反射的使用

    1. 查看结构体类型、字段、方法、匿名字段、字段tag

    package mainimport ("fmt""reflect" )//定义结构体 type User struct {Id intName string Age int }type Boy struct {UserAddr string `db:"addr"` }//绑定方法 func (u User) Say() {fmt.Println("Hello") }func (u User) Eat() {fmt.Println("Eat price") }func PrintInfo(i interface{}){t := reflect.TypeOf(i)fmt.Println("结构体类型:", t)v := reflect.ValueOf(i)fmt.Println("结构体的值:", v)fmt.Println("\n结构体字段名称 :字段类型 \t : 值 \t\t : 是否为匿名字段: 字段tag为db的值")for i := 0; i < t.NumField(); i++ {f := t.Field(i)val := v.Field(i)fmt.Printf("%s\t\t : %v\t : %v\t : %v\t : %v\n", f.Name, f.Type, val.Interface(), f.Anonymous, f.Tag.Get("db"))//fieldByName, _ := t.FieldByName(f.Name)//fmt.Println(fieldByName)}fmt.Println("\n结构体绑定的方法:方法类型\t:方法地址 \t\t 方法索引 ")for i := 0; i < t.NumMethod(); i++ {m := t.Method(i)fmt.Printf("%s\t\t %v\t %v\t %v\n", m.Name, m.Type, &m.Func, m.Index)//Func, _ := t.MethodByName(m.Name)//fmt.Println(Func)} }func main() {b := Boy{User: User{Id: 1,Name: "Yuan",Age: 26,},Addr: "chengdu",}PrintInfo(b) }

    2. 修改结构体字段的值

    package mainimport ("fmt""reflect" )//定义结构体 type User struct {Id intName stringAge int }func SetValue(i interface{}){v := reflect.ValueOf(i)//获取指针指向的元素elem:= v.Elem()//获取要修改的字段name := elem.FieldByName("Name")if name.Kind() == reflect.String && name.CanSet(){name.SetString("老大")} }func main() {u := User{Id: 1,Name: "Yuan",Age: 26,}fmt.Println("修改前:", u)SetValue(&u)fmt.Println("修改后:", u) }

    3. 调用结构体绑定的方法

    package mainimport ("fmt""reflect" )//定义结构体 type User struct {Id intName stringAge int }//绑定方法 func (u User) Say(s string) {fmt.Println("Hello", s) }func (u User) Eat() {fmt.Println("Eat price.") }func CallFunc(i interface{}){v := reflect.ValueOf(i)//获取结构体绑定的方法sayFunc := v.MethodByName("Say")//构建调用参数args := []reflect.Value{reflect.ValueOf("world!")}//调用方法sayFunc.Call(args)//调用无参方法eatFunc := v.MethodByName("Eat")args = []reflect.Value{}eatFunc.Call(args) //这里必须传值,不能为空 }func main() {u := User{Id: 1,Name: "Yuan",Age: 26,}CallFunc(&u) }

    反射的牛刀小试

    在Kubernetes中的资源配置文件的主要形式是yaml格式的,同时Kubernetes还支持json格式和proto格式的配置文件,下面我们自己可以实现一个简单的ini格式配置文件的解析,使用案例如下:

    package mainfunc main() {// 制作测试数据var conf Configconf.ServerConf.IP = "192.168.0.1"conf.ServerConf.Port = 8080conf.ClientConf.Username = "Yuan"conf.ClientConf.Password = "Abcd123456"//将结构体信息写入配置文件StructToFile("./config.ini", conf)var config Config// 从配置文件解析到结构体中FileToStruct("./config.ini", &config)logger.Printf("%#v", config) }

    执行上面的代码会生成config.ini配置文件并输出从配置文件解析的结构体信息到控制台

    其中生成的config.in文件内容如下:

    [SERVER] ip = 192.168.0.1 port = 8080[CLIENT] username = Yuan password = Abcd123456

    控制台输出为:

    main.go:18: main.Config{ServerConf:main.ServerConfig{IP:"192.168.0.1", Port:8080}, ClientConf:main.ClientConfig{Username:"Yuan", Password:"Abcd123456"}}

    项目源码地址

    总结

    以上是生活随笔为你收集整理的反射(reflect)机制的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。