反射

反射

在运行时反射是程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。它同时也是造成迷惑的来源。反射可以在运行时检查类型和变量,例如它的大小、方法和 动态 的调用这些方法。这对于没有源代码的包尤其有用。

Golang 实现反射是通过 reflect 包来实现的, 让原本是静态类型的 go 具备了很多动态类型语言的特征。reflect 包有两个数据类型,一个是 Type,一个是 Value。它定义了两个重要的类型,Type 和 Value. Type 就是定义的类型的一个数据类型,Value 是值的类型, TypeOf 和 ValueOf 是获取 Type 和 Value 的方法。一个 Type 表示一个 Go 类型.它是一个接口, 有许多方法来区分类型以及检查它们的组成部分, 例如一个结构体的成员或一个函数的参数等. 唯一能反映 reflect.Type 实现的是接口的类型描述信息,也正是这个实体标识了接口值的动态类型.

从接口类型变量到反射类型对象

反射可以将接口类型变量转换为反射类型对象,这里的反射类型指的是 reflect.Type 和 reflect.Value.将接口类型变量转换为反射类型变量,是通过 reflect 包的 TypeOf 和 ValueOf 实现的。在 reflect 包中的 TypeOf 和 ValueOf 的定义:

// TypeOf返回表示i的动态类型的反射Type。如果i是nil接口值,则TypeOf返回nil。

func TypeOf(i interface{}) Type

// ValueOf返回一个新的Value,初始化为存储在接口i中的具体值。ValueOf(nil)返回零值

func ValueOf(i interface{}) Value

然后我们可以使用 reflect.ValueOf 和 reflect.TypeOf 将接口类型变量分别转换为反射类型:

var p int = 10
v1 := reflect.ValueOf(p)//返回Value类型对象,值为10

t1 := reflect.TypeOf(p)//返回Type类型对象,值为int

fmt.Println("v1:",v1)

fmt.Println("t1",t1)

v2 := reflect.ValueOf(&p)//返回Value类型对象,值为&p,变量p的地址

t2 := reflect.TypeOf(&p)//返回Type类型对象,值为*int

fmt.Println("v2:",v2)

fmt.Println("t2:",t2)

/**
v1: 10
t1: int
v2: 0xc4200180b8
t2: *int
**/

其中 v1 和 v2 中包含了接口中的实际值,t1 和 t2 中包含了接口中的实际类型.由于 reflect.ValueOf 和 reflect.TypeOf 的参数类型都是 interface{},空接口类型,而返回值的类型是 reflect.Value 和 reflect.Type,中间的转换由 reflect 包来实现。所以就实现了接口类型变量到反射类型对象的转换。

将反射类型对象转化为接口类型变量

这里根据一个 reflect.Value 类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。事实上,这个方法会把 type 和 value 信息打包并填充到一个接口变量中,然后返回。下面我们来看看在 reflect 包中 Value 的定义:

func (v Value) Interface() (i interface{})

// 接口将v的当前值作为接口{}返回。它相当于:

var i interface{} = (v's underlying value)

// 如果通过访问未导出的struct字段获得Value,则会发生混乱。

然后我们可以使用 Value 将反射类型转换为接口类型变量:

var a int = 10

v1 := reflect.ValueOf(&a) //返回Value类型对象,值为&a,变量a的地址

t1 := reflect.TypeOf(&a)  //返回Type类型对象,值为*int

fmt.Println("v1:",v1)

fmt.Println("t1:",t1)

v2 := v1.Interface() //返回空接口变量

v3 := v2.(*int)     //类型断言,断定v1中type=*int

fmt.Printf("%T %v\n", v3, v3)

fmt.Println("v3:",*v3)

/**
    v1: 0xc420082010
    t1: *int
    *int 0xc420082010
    v3: 10
**/

reflect.ValueOf 的逆操作是 reflect.Value.Interface 方法.它返回一个 interface{}类型,装载着与 reflect.Value 相同的具体值,这样我们就可以将“反射类型对象”转换为“接口类型变量。

其实 reflect.Value 和 interface{} 都能装载任意的值。有所不同的是, 一个空的接口隐藏了值内部的表示方式和所有方法, 因此只有我们知道具体的动态类型才能使用类型断言来访问内部的值(就像上面那样), 内部值我们没法访问. 相比之下, 一个 Value 则有很多方法来检查其内容, 无论它的具体类型是什么。

修改反射类型对象

果要修改反射类型对象,其值必须是“addressable” 在上面第一种反射定律将“接口类型变量”转换为“反射类型对象”我们可以知道,反射对象包含了接口变量中存储的值以及类型。如果反射对象中包含的值是原始值,那么可以通过反射对象修改原始值,如果反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址),那么该反射对象是不可以修改的。

那么我们可以通过 CanSet 函数可以判定反射对象是否可以修改。

// CanSet报告是否可以更改v的值.仅当值可寻址且未通过使用未导出的struct字段获取时,才能更改值。如果CanSet返回false,则调用Set或任何特定于类型的setter(例如,SetBool,SetInt)将会发生混乱。

func (v Value) CanSet() bool

注意,CanSet 返回 false,值是不可以修改的,如果返回 true 则是可以修改的。

var p float64 = 3.4

v1 := reflect.ValueOf(&p)

if v1.Kind() == reflect.Ptr && !v1.Elem().CanSet() { //判断是否为指针类型,元素是否可以修改
   fmt.Println("cannot set value")
   return
} else {
   v1 = v1.Elem() //实际取得的对象
   fmt.Println("CanSet return bool:", v1.CanSet())
}

v1.SetFloat(6.1)

fmt.Println("p=",p)

/**
CanSet return bool: true
p= 6.1
**/

从运行结果看值修改成功了,但是这里出现了 Elem 函数作用是用来获取原始值对应的反射对象:

// Elem返回接口v包含的值或指针v指向的值。如果v的Kind不是Interface或Ptr,它会发生恐慌。如果v为nil,则返回零值。

func (v Value) Elem() Value

在这里要修改变量 p 的值,首先就要通过 reflect.ValueOf 来获取 p 的值类型,refelct.ValueOf 返回的值类型是变量 p 一份值拷贝,要修改变量 p 就要传递 p 的地址,从而返回 p 的地址对象,才可以进行对 p 变量值对修改操作。在得到 p 变量的地址值类型之后先调用 Elem()返回地址指针指向的值的 Value 封装。然后通过 Set 方法进行修改赋值。

通过反射可以很容易的修改变量的值,我们首先要通过反射拿到这个字段的地址值类型,然后去判断反射返回类型是否为 reflect.Ptr 指针类型(通过指针才能操作对象地址中的值)同时还要判断这个元素是否可以修改,通过 Kind()和 Set 来修改字段的值,然后就可以拿到我们修改的值了.

因此在反射中使用反射包提供 refelct.TypeOf 和 refelct.ValueOf 方法获得接口对象的类型,值和方法等。通过反射修改字段值等时候需要传入地址类型,并且需要检查反射返回值类型是否为 refelct.Ptr,检查字段是否 CanSet,检查字段是存在,然后通过 Kind()来帮助赋值相应对类型值。

案例分析

结构体标签

package main

import (
	"fmt"
	"reflect"
)

type User struct {
     Name   string `json:"name"`
     Gender string `json:"gender"`
     Age    int    `json:"age"`
}

func main()  {
     types := reflect.TypeOf(&User{}).Elem()
     value := reflect.ValueOf(&User{}).Elem()
     fmt.Println("values Numfield:",value.NumField())
     for i:=0;i<types.NumField();i++ {
        m := types.Field(i).Tag.Get("json")
        fmt.Println(m)
     }
}

/**
    values Numfield: 3
    name
    gender
    age
**/