反射

golang的反射主要包含两个类型:reflect.Typereflect.Value,他们主要由函数reflect.TypeOf(变量名)以及reflect.ValueOf(变量名)创建而来。其中Type用于表示目标变量的类型的元数据,而通过Value则是代表变量的值(可以对值进行修改、函数值进行函数调用等)。

  1. 在运行时动态获取变量的各种信息,比如变量的类型(type)、类别(kind)

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

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

  4. 使用反射需要引入包import "reflect"

反射的基本函数

package main

import (
	"fmt"
	"reflect"
)

func main() {

	var n int64 = 10

	// 获取变量类型信息 reflect.Type
	rTyp := reflect.TypeOf(n)
	fmt.Printf("rType: type=%T, toString=%s\n", rTyp, rTyp.String())

	// 获取变量值信息 reflect.Value
	rVal := reflect.ValueOf(n)
	fmt.Printf("rVal: type=%T, toString=%s, originValue=%d\n", rVal, rVal.String(), rVal.Int()) // 转换为原始类型,如果调用的方法与实际类型不匹配,会发生panic异常

	// 获取变量对应的kind,type代表变量的具体类型,而kind代表变量的分类类型
	// 如果是基本类型,kind==type,比如 kind=int64 type=int64
	// 如果不是基本类型,比如struct,type与kind一般不同,比如 kind=struct type=main.Student
	kind1 := rTyp.Kind()
	kind2 := rVal.Kind()
	fmt.Printf("kind=%s, %s", kind1, kind2)


	// 根据 rVal 获取变量值,因为不知道是什么类型,所以以interface的方式返回
	iV := rVal.Interface()
	fmt.Printf("iV: type=%T, originValue=%v\n ", iV, iV)

	// 将interface{} 通过断言转换成需要的类型
	n2 := iV.(int64)
	fmt.Println(n2)
}

返回结果:

rType: type=*reflect.rtype, toString=int64
rVal: type=reflect.Value, toString=<int64 Value>, originValue=10
kind=int64, int64iV: type=int64, originValue=10
10

通过反射修改值

在 Golang 中,通过 反射 的方法reflect.ValueOf传入的不管是变量的值还是地址,通过反射都不可以直接修改变量的值,如果尝试修改,将会引发一个panic

package main

import (
	"fmt"
	"reflect"
)

func main() {
	i := 10
	rVal := reflect.ValueOf(i) // panic: reflect: reflect.Value.SetInt using unaddressable value
	rVal.SetInt(11)
	fmt.Println(i)
}

上面的异常是说,SetInt方法,也就是修改变量值的反射方法,不可以用在一个不可取地址的reflect.Value上,通过reflect.ValueOf传入变量获取的reflect.Value都是不可取地址值。这是因为无论什么类型的变量,relect.Value都是存储变量的指针(地址)。

  1. 值类型值传入

    var x float64 = 3.4
    v := reflect.ValueOf(x) // x 值传入,ValueOf接收到的x已经不是原来的x
    v.SetFloat(7.1) // 这句话及时执行成功,也无法修改变量x的值
  2. 值类型取地址传入

    var x float64 = 3.4
    p := reflect.ValueOf(&x) // 注意:获取 x 的地址
    fmt.Println("type of p:", p.Type())
    fmt.Println("settability of p:", p.CanSet()) // CanSet用来判断是否是可取地址的, 此处返回false
    // 此处仍然是不可取地址的,因为p内部存储的是变量的地址,需要通过类似 *x 的方式真正访问变量空间
  3. 使用Elem()方法访问变量内存

    v := p.Elem()
    fmt.Println("settability of v:", v.CanSet()) // true
    v.SetFloat(7.1)
    fmt.Println(v.Interface()) // 7.1
    fmt.Println(x) // 7.1
  4. 结构体值访问

    type T struct {
        A int
        B string
    }
    t := T{23, "skidoo"}
    s := reflect.ValueOf(&t).Elem()
    typeOfT := s.Type()
    for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        fmt.Printf("%d: %s %s = %v\n", i,
            typeOfT.Field(i).Name, f.Type(), f.Interface())
    }
  5. 结构体字段值修改

    s.Field(0).SetInt(77)
    s.Field(1).SetString("Sunset Strip")
    fmt.Println("t is now", t)

反射操作方法与函数

// 获取add函数Type
vType:=reflect.TypeOf(add)

// 返回func类型的参数个数,如果不是函数,将会panic 
numIn:=vType.NumIn()

addIn:=make([]reflect.Type,numIn) 
for i:=0;i<numIn;i++{ addIn[i]=vType.In(i) 

最后更新于