反射

引入

在我们前面学结构体的序列化和反序列化时,我们通过给结构体属性加标签,解决了序列化的时候可以将字段的名字根据我的tag进行序列化,这里就用到了我们的反射,所以反射到底是什么东西,我们接着往下看。

基本介绍

1.反射可以在运行时动态获取变量的各种信息,比如变量的类型和类别。

2.如果是结构体变量,还可以获取到结构体本身的信息,如字段、方法

3.通过反射,可以修改变量的值,可以调节关联的方法

4.使用反射,需要import “reflect

用途:

  • 实现通用函数(如 fmt.Printlnjson.Marshal
  • 框架开发(如 ORM、API 绑定)
  • 动态调用方法或字段

通过reflect.TypeOf(v)reflect.ValueOf(v)去获得变量的类型或值,然后通过一些方法可以对其进行操作。

举个例子来方便理解:写一个框架的的时候你并不知道会传入什么变量,通过反射可以直接获取该变量的属性、方法等等

在实际开发中,常会实现变量,接口和reflect.Value互相转化。

 var student Stu
 func test(b interface{}) {
     /*1. 如何将 interface{} 转成 reflect.Value*/
     rVal := reflect.ValueOf(b)
 ​
     /*2. 如何将 reflect.Value -> interface{}*/
     iVal := rVal.Interface()
     /*3. 如何将interface{}转成原来的类型*/
     v:=iVal.(Stu)
 }

PS:学到这TTDR挺懵逼的,emm希望慢慢理解吧

快速入门

我们就来写两个案例:实现基本数据类型/结构体类型,interface{},reflect.Value的反射操作

我们先来看对基本数据类型的:

 package main
 ​
 import (
  "fmt"
  "reflect"
 )
 ​
 func reflectTest01(b interface{}) {
  /*通过反射获取传入变量的type,kind,值*/
  /*1.获得reflect.Type*/
  rTyp := reflect.TypeOf(b)
  fmt.Println("rtype=", rTyp)
  /*2.获取到reflect.Value*/
  rVal := reflect.ValueOf(b)
  n2 := 2 + rVal.Int()
  fmt.Println("n2=", n2)
  fmt.Printf("rVal=%v,type=%T", rVal, rVal)
  /*将rval转为interface*/
  iv := rVal.Interface()
  num2 := iv.(int) /*类型断言*/
  fmt.Println("num2=", num2)
 }
 func main() {
  var num int = 100
  reflectTest01(num)
 }

注意这里rVal的值虽然是整数,但是它的类型不是整形,不能直接和整数进行运算,但是提供了一些方法

image-20260128194604198

我们再来看看对结构体的反射

 func reflectTest02(b interface{}) {
  /*通过反射获取传入变量的type,kind,值*/
  /*1.获得reflect.Type*/
  rTyp := reflect.TypeOf(b)
  fmt.Println("rtype=", rTyp)
  /*2.获取到reflect.Value*/
  rVal := reflect.ValueOf(b)
 ​
  fmt.Printf("rVal=%v,type=%T\n", rVal, rVal)
  /*将rval转为interface*/
  iv := rVal.Interface()
  fmt.Printf("iv=%[1]v,type=%[1]T\n", iv)
  /*上面的方式如果要取iv.Name会报错,反射的本质是运行时,在运行的瞬间可以确认iv的值和类型但是在编译阶段不能确定iv是什么*/
  /*所以要类型断言,这里可以改用switch变得更灵活*/
  stu, ok := iv.(Student)
  if ok {
  fmt.Println(stu.Name)
  }
 }
 func main() {
  stu := Student{
  Name: "Tom",
  Age:  10,
  }
  reflectTest02(stu)
 }

注意事项和细节

1.reflect.Value,Kind,获得变量的类别,返回的是一个常量,我们上链接。Kind

 kind1 := rVal.Kind()
 kind2 := rTyp.Kind()

二者的值是一样的。。

2.Kind是type的一个父类,Type是类型,Kind是类别,二者可能相同,也可能不同,

结构体的Type是包名.Struct1,Kind 是Struct1

3.使用反射来获取变量的值并且返回对应的值,要求类型匹配,比如x是int,就必须用reflect.Value(x).Int()。

4.通过反射来修改变量,注意当使用SetXxx来设置需要通过对应指针类型来完成,这样才能改变传入变量的值,同时需要使用reflect.Value.Elem()

 package main
 ​
 import (
  "fmt"
  "reflect"
 )
 ​
 func reflect01(b interface{}) {
  rVal := reflect.ValueOf(b)
  fmt.Printf("rVal kind = %v", rVal.Kind())
  rVal.SetInt(20)
 }
 func main() {
  var num int = 10
  reflect01(&num)
  fmt.Println("num=", num)
 }

我们发现这里报错了,因为这里的rVal为指针类型,我们这里不能直接对它的值进行操作,SetInt只能对非地址类型的进行操作,我们要用Elem

 package main
 ​
 import (
  "fmt"
  "reflect"
 )
 ​
 func reflect01(b interface{}) {
  rVal := reflect.ValueOf(b)
  fmt.Printf("rVal kind = %v\n", rVal.Kind())
  rVal.Elem().SetInt(20)
 }
 func main() {
  var num int = 10
  reflect01(&num)
  fmt.Println("num=", num)
 }
 ​
image-20260128214641198

案例实践

1.使用反射来遍历结构体的字段可调用结构体的方法并获取结构体标签的值,并且可以修改字段值

方法:MethodCall

 package main
 ​
 import (
     "fmt"
     "reflect"
     "encoding/json"
 )
 ​
 type Monster struct {
     Name   string  `json:"monster_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
     }
     if kd == reflect.Ptr && val.Elem().Kind() == reflect.Struct {
         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() {
     var a Monster = Monster{
         Name:  "黄狮子",
         Age:   408,
         Score: 92.8,
         Sex:   "男", 
     }
 ​
     /*Marshal 就是通过反射获取到 struct 的 tag 值*/
     result, _ := json.Marshal(a)
     fmt.Println("json result:", string(result))
 ​
     TestStruct(&a)
     fmt.Println(a)
 }

2.定义两个函数test1和test2,定义一个适配器函数用作统一处理接口。

 package test
 ​
 import (
     "fmt"
     "reflect"
 )
 ​
 /* 假设 t 是一个日志工具*/
 type T struct{}
 ​
 func (t *T) Log(args ...interface{}) {
     fmt.Println(args...)
 }
 ​
 func TestReflectFunc(t *T) {
     call1 := func(v1 int, v2 int) {
         t.Log(v1, v2)
     }
 ​
     call2 := func(v1 int, v2 int, s string) {
         t.Log(v1, v2, s)
     }
 ​
     bridge := func(call interface{}, args ...interface{}) {
         function := reflect.ValueOf(call)
 ​
         inValue := make([]reflect.Value, len(args))
         for i, arg := range args {
             inValue[i] = reflect.ValueOf(arg)
         }
 ​
         function.Call(inValue)
     }
 ​
     bridge(call1, 1, 2)
     bridge(call2, 1, 2, "test2")
 }

结语

Ttdr稀里糊涂学完了

找前辈聊了点天,有点感悟,放在下面了

要学计算机,第一课就是别抵触英文。多用 Google 配合英文关键词去检索,国外虽然也有类似 CSDN 那样的“shit 平台”,但整体质量还是高不少——像 Stack Overflow、Reddit、官方文档这些地方,信息更靠谱。博客也可以看,但得注意甄别,因为博客属于二手知识,很多人只是记录自己的学习过程,错的也照记;真正高质量的博客,应该是在输出观点和结论,而不是像上课记笔记一样记录东西。最重要的是多去看开源项目,在学任何一个知识点时,得多问几个问题:它的原理是什么?作用是什么?用在什么地方?能做到什么程度?为什么需要它?比如说Golang的反射,为什么 Java 有它,Go 也需要它,而 C 却没有?还有没事去 Go 官网把语言规范通读一遍,收获就已经很大了。不知道从哪获取知识?就先从语言的官方渠道入手,一个语言要推销自己,官网肯定会想尽办法把信息组织得清晰、友好、便于学习。Go 的 GitHub 上还能看到很多新特性的提案,想长见识可以翻一翻,也能看到 Go 的 Roadmap;所有特性都是先在那里提出来、讨论、批准,才进入实现的。如果Go加一些 人可能会比较难懂的知识,go 官方博客是会发布相关的文章的比如反射,还有 channel 的一些设计模式,最近的 1.26 的绿茶gc,这篇很推荐去读。

暂无评论

发送评论 编辑评论


				
上一篇
下一篇