Go语言|利用反射reflect.Value修改变量的值

948 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

引言

reflect包实现了运行时反射,允许程序操作任意类型的对象。典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值。调用ValueOf函数返回一个Value类型值,该值代表运行时的数据。Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值。

反射reflect.Value修改变量的值

reflect.Value是通过reflect.ValueOf(X)获得的,只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值,即:要修改反射类型的对象就一定要保证其值是“addressable”的。

也就是说:要想修改一个变量的值,那么必须通过该变量的指针地址 , 取消指针的引用 。通过refPtrVal := reflect.Valueof( &var )的方式获取指针类型,你使用refPtrVal.elem( ).set(一个新的reflect.Value)来进行更改,传递给set()的值也必须是一个reflect.value。

这里需要一个方法:

func (Value) Elem

func (v Value) Elem() Value

Elem返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装。如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值。

如果你的变量是一个指针、map、slice、channel、Array。那么你可以使用reflect.Typeof(v).Elem()来确定包含的类型。

package main

import (
   "fmt"
   "reflect"
)

func main() {

   var num float64 = 3.1415926
   fmt.Println("num的数值:", num)

   //需要操作指针
   //通过reflect.ValueOf获取num中的reflect.Value,注意,参数必须是指针才能修改其值
   pointer := reflect.ValueOf(&num)
   newValue := pointer.Elem()

   fmt.Println("类型 :", newValue.Type()) //float64
   fmt.Println("是否可以修改:", newValue.CanSet())

   // 重新赋值
   newValue.SetFloat(666)
   fmt.Println("新的数值:", num)


   // 如果reflect.ValueOf的参数不是指针,会如何?

   //尝试直接修改
   //value := reflect.ValueOf(num)
   //value.SetFloat(666) //panic: reflect: reflect.Value.SetFloat using unaddressable value
   //fmt.Println(value.CanSet()) //false

   //pointer = reflect.ValueOf(num)
   //newValue = value.Elem() // 如果非指针,这里直接panic,“panic: reflect: call of reflect.Value.Elem on float64 Value”
}

运行结果

num的数值: 3.1415926
类型 : float64
是否可以修改: true
新的数值: 666

代码解释:

  1. 需要传入的参数是 *float64 这个指针,然后可以通过pointer.Elem()去获取所指向的Value,注意一定要是指针

  2. 如果传入的参数不是指针,而是变量,那么

    • 通过Elem获取原始值对应的对象则直接panic
    • 通过CanSet方法查询是否可以设置返回false
  3. newValue.CantSet()表示是否可以重新设置其值,如果输出的是true则可修改,否则不能修改,修改完之后再进行打印发现真的已经修改了。

  4. reflect.Value.Elem() 表示获取原始值对应的反射对象,只有原始对象才能修改,当前反射对象是不能修改的

  5. 也就是说如果要修改反射类型对象,其值必须是“addressable”【对应的要传入的是指针,同时要通过Elem方法获取原始值对应的反射对象】

  6. struct 或者 struct 的嵌套都是一样的判断处理方式

尝试修改结构体中的字段数值:

package main

import (
   "fmt"
   "reflect"
)

type Student struct {
   Name string
   Age int
   School string
}
func main()  {
   /*
      通过反射,来更改对象的数值:前提是数据可以被更改
   */
   s1:=Student{"王富贵",20,"MIT"}
   fmt.Printf("%T\n",s1) //main.Student
   p1:=&s1
   fmt.Printf("%T\n",p1) //*main.Student
   fmt.Println(s1.Name)
   fmt.Println((*p1).Name,p1.Name)

   v1:= reflect.ValueOf(&s1) // value

   if v1.Kind()==reflect.Ptr{
      fmt.Println(v1.Elem().CanSet())
      v1 = v1.Elem()
   }

   f1:=v1.FieldByName("Name")
   f1.SetString("诸葛青")
   f3:=v1.FieldByName("School")
   f3.SetString("蓝翔")
   fmt.Println(s1)

}

运行结果:

main.Student
*main.Student
王富贵
王富贵 王富贵
true
{诸葛青 20 蓝翔}