Go反射应用技巧

0 阅读7分钟

反射是指一类应用.它们能够自描述和自控制.这类应用通过采用某种机制来实现对自己的行为描述和监测.并能根据自身行为的状态和结果.调整或修改应用所描述行为的状态和相关的语义.

每种语言的反射模型都不同.并且有些语言根本不支持反射.Go语言实现了反射.反射机制就是在运行时动态的调用对象的方法和属性.

1).接口和反射:

deepseek_mermaid_20260502_ba8843.png

变量包括类型和值两部分.类型包括静态类型和具体类型.静态类型是指在编码时看见的类型(如int string等).具体类型是指在运行时系统看见的类型.

类型的断言Cassertion是否能成功.取决于变量的具体类型.而不是静态类型.因此一个只读变量如果它的具体类型也实现了可写方法.它也可以被类型断言为写入者.

反射就是建立在类型之上的.Go语言指定类型的变量的类型是静态的.(也就是指定int string这些变量.它们的类型是静态类型).在创建变量的时候就已经确定了.反射主要与Go语言接口(interface)类型相关(它的类型是具体类型).只有接口类型才有反射.

在Go语言实现中.每个接口变量都有对应的一对数据.这个配对中记录了实际变量的值和类型.形式(value,type).

value是实际变量的值.type是实际变量的类型.一个interface{}类型的变量包含了两个指针.一个指针指向值得类型(对应的具体类型).另外一个指针指向实际的值(对应的value).

2).反射原理:

TypeOf函数和ValueOf函数:

reflect.ValueOf()函数定义:

func ValueOf(i interface{}) Value{...}

ValueOf函数用来获取输入参数接口中的数据的值.如果接口为空.则返回0.

reflect.TypeOf()函数的定义:

func TypeOf(i interface{}) Type{...}

TypeOf函数用来动态获取输入参数接口中的值的类型.如果接口为空.则返回nil.

示例:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var money float32 = 88.38
    fmt.Println(reflect.TypeOf(money))
    fmt.Println(reflect.ValueOf(money))
}

执行结果:

从上面结果可以看出反射可以将接口类型变量转换为反射类型对象.反射类型指的是reflect.Type和reflect.Value这两种.

当执行reflect.ValueOf()函数后.就得到一个类型为reflect.Value的变量.可以通过它本身的Interface()方法获得接口变量的真实内容.然后可以通过类型判断进行转换.转换为原有真实类型.这个真实类型可能是已知原有类型.也有可能是未知原有类型.

已知原有类型:

已知原有类型(进行强制转换).已知类型后.转换为其它对应类型的做法为直接通过Interface()方法强制转换.

realValue := value.Interface().(已知的类型)

示例:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var money float32 = 66.68
    pointer := reflect.ValueOf(&money)
    value := reflect.ValueOf(money)

    convertPointer := pointer.Interface().(*float32)
    convertValue := value.Interface().(float32)
    fmt.Println(convertPointer)
    fmt.Println(convertValue)
}

执行结果:

在转换的时候.对类型要求非常严格.如果转换的类型不完全符合.则直接触发panic.同时要区分是指针还是值.也就是说反射可以将反射类型对象重新转换为接口类型变量.

未知原有类型:

未知原有类型(遍历探测其Filed).

示例:

import (
    "fmt"
    "reflect"
)

type Programmer struct {
    Id    int
    Name  string
    Level int
}

func (u Programmer) ReflectCallFunc() {
    fmt.Println("barry")
}

func main() {
    pro := Programmer{1, "Barry", 8}
    GetFiledAndMethod(pro)
}

func GetFiledAndMethod(input interface{}) {
    getType := reflect.TypeOf(input)
    fmt.Println("Type is:", getType.Name())
    getValue := reflect.ValueOf(input)
    fmt.Println("Value is:", getValue)
    for i := 0; i < getType.NumMethod(); i++ {
       field := getType.Field(i)
       value := getValue.Field(i).Interface()
       fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
    }

    for i := 0; i < getType.NumMethod(); i++ {
       m := getType.Method(i)
       fmt.Printf("%s: %v\n", m.Name, m.Type)
    }
}

执行结果:

具体变量类型步骤:

1.先获取interface的reflect.Type.然后通过NumField进行遍历.

2.在通过reflect.Type的Field获取其Field.

3.最后通过Field的Interface()得到对应的value.

方法获取步骤:

1.先获取interface的reflect.Type.然后通过NumMethod进行遍历.

2.再分别通过reflect.Type的Method获取对应的真实方法.

3.最后通过结果取其Name和Type得知具体的方法名.

设置实际值:

deepseek_mermaid_20260502_637254.png

reflect.Value通过reflect.ValueOf(interface)方法获得的.只有当参数interface是指针的时候.才可以通过reflect.Value修改实际变量的值.即如果要修改反射类型的对象就一定要保证其值是可寻址的.

示例:

func main() {
    var money float32 = 66.66
    fmt.Println("指针原来的值:", money)

    pointer := reflect.ValueOf(&money)
    newValue := pointer.Elem()
    fmt.Println("指针的类型是:", newValue.Type())
    fmt.Println("指针是否可设置:", newValue.CanSet())

    //重新赋值.
    newValue.SetFloat(88.88)
    fmt.Println("指针的新值是:", money)
}

执行结果:

1.需要传入的参数是*float32这个指针.然后通过pointer.Elem去获取所指向的Value.参数类型一定要是指针.

2.如果传入的参数不是指针.而是变量.那么通过Elem获取原始值对应的对象值则会直接触发panic.

3.通过CanSet方法查询是否可以重新设置值.

4.newValue.CanSet表示是否可以重新设置值.如果输出的是true则可以修改.否则不可以修改.reflect.Value.Elem表示获取原始值对应的反射对象.只有原始对象才能修改.当前反射对象不可以修改.如果要修改反射类型对象.其值必须是可寻址的.

通过reflect.ValueOf函数进行调用:

image.png

示例:

package main

import (
    "fmt"
    "reflect"
)

type Programmer struct {
    Id    int
    Name  string
    Level int
}

func (p Programmer) CallFuncAndArgs(name string, age int) {
    fmt.Println("CallFuncAndArgs name:", name, "age:", age)
}

func (p Programmer) CallFuncNoArgs() {
    fmt.Println("CallFuncNoArgs ")
}

func main() {
    program := Programmer{1, "Toey", 18}
    getValue := reflect.ValueOf(program)
    methodValue := getValue.MethodByName("CallFuncAndArgs")
    arg := []reflect.Value{reflect.ValueOf("barry"), reflect.ValueOf(20)}
    methodValue.Call(arg)

    methodValue = getValue.MethodByName("CallFuncNoArgs")
    arg = make([]reflect.Value, 0)
    methodValue.Call(arg)
}

执行结果:

如果要通过反射调用.首先要将方法注册.也就是通过methodByName()方法获取方法值methodValue对象.然后通过调用methodValue对象Call方法.

3).反射性能:

Go语言反射设计如下.

type_ := reflect.TypeOf.TypeOf(obj)

field,_ := type_.FieldByName("hi")

如果要读取的field对象是reflect.StructField类型.但是没有办法用来读取对应对象上的值.如果要读取值.则需要用另外一套对象.而不是type的反射.

type_ := reflect.ValueOf(obj)

fieldValue := type_.FieldByName("hi")

这里读取的fieldValue类型是reflect.Value.它是一个具体的值.而不是一个可复用的反射对象.每次反射都需要分配请求的内存并返回指向它的指针给这个reflect.Value结构体.并且还涉及垃圾回收.

1).频繁的进行内存分配以及后续的GC.

2).reflect反射实现里面有大量的枚举 类型转换 for循环等.

反射三大法则:

deepseek_mermaid_20260502_031792.png

反射可以将"接口变量"转换为"反射对象"

反射是一种检测存储在接口变量中的值和类型的机制.通过reflect包的一些函数.可以把接口转换为反射定义的对象.reflect包的常用函数如下.

reflect.ValueOf({} interface) reflect.Value:

获取某个变量的值.但值是通过reflect.Value对象描述的.

reflect.TypeOf({} interface) reflect.Type:

获取某个变量的值.但值是通过reflect.Type对象描述的.可以直接用fmt.Println打印.

reflect.Value.Kind()  Kind:

获取变量值的底层类型.底层类型可能是Int Float Struct Slice等.

reflect.Value.Type() reflect.Type:

获取变量值的类型.效果等同于reflect.TypeOf.

反射可以将"反射对象"转换为"接口变量":

和法则一相反.法则二描述的是.从反射对象到接口变量的转换.和物理学反射类型.Go语言中的反射也能创造自己的反面对象.根据一个reflect.Value类型的变量.可以使用Interface()函数恢复其接口类型的值.事实上.这个方法会type和Value信息打包并填充到一个接口变量中.然后返回.Interface()方法声明如下.

func (v value) Interface() interface{}

然后可以通过断言.恢复底层的具体值.代码如下.

y:= v.Interface().(float64)

fmt.Println(y)

Interface()方法就是用来实现将反射对象转换成接口变量的一个桥梁.

注意:如果Value是接口体的非导出字段.则调用该函数会触发panic.

如果要修改"反射对象".则其值必须是"可写的"(settable)

当使用reflect.TypeOf和reflect.ValueOf时.如果传递的不是接口变量的指针.反射环境里变量值始终将只是真实环境里的一个副本.对该反射对象进行修改.并不能反射到真实环境里.在反射规则了.需要注意如下.

1.不是接收变量指针创建的反射对象.是不具备"可写性的".

2.是否具备可写性.可使用CanSet()函数来获取.

3.对不具备可写性的对象进行修改是没有意义的.也认为是不合法的.会报错.

反射对象具备可写性.

创建反射对象时传入变量的指针.

使用Elem()函数返回指针指向的数据.