Go语言反射规则浅析

前面讲解了 Value 和 Type 的基本概念,本节重点讲解反射对象 Value、Type 和类型实例之间的相互转化。实例、Value、Type 三者之间的转换关系如下图所示。

反射对象关系
图:反射对象关系

反射 API

反射 API 的分类总结如下。

1) 从实例到 Value

通过实例获取 Value 对象,直接使用 reflect.ValueOf() 函数。例如:

func ValueOf(i interface {}) Value

2) 从实例到 Type

通过实例获取反射对象的 Type,直接使用 reflect.TypeOf() 函数。例如:

func TypeOf(i interface{}) Type

3) 从 Type 到 Value

Type 里面只有类型信息,所以直接从一个 Type 接口变量里面是无法获得实例的 Value 的,但可以通过该 Type 构建一个新实例的 Value。reflect 包提供了两种方法,示例如下:

//New 返回的是一个 Value,该 Value 的 type 为 PtrTo(typ),即 Value 的 Type 是指定 typ 的指针类型
func New(typ Type) Value
//Zero 返回的是一个 typ 类型的零佳,注意返回的 Value 不能寻址,位不可改变
func Zero(typ Type) Value

如果知道一个类型值的底层存放地址,则还有一个函数是可以依据 type 和该地址值恢复出 Value 的。例如:

func NewAt(typ Type, p unsafe.Pointer) Value

4) 从 Value 到 Type

从反射对象 Value 到 Type 可以直接调用 Value 的方法,因为 Value 内部存放着到 Type 类型的指针。例如:

func (v Value) Type() Type

5) 从 Value 到实例

Value 本身就包含类型和值信息,reflect 提供了丰富的方法来实现从 Value 到实例的转换。例如:

//该方法最通用,用来将 Value 转换为空接口,该空接口内部存放具体类型实例
//可以使用接口类型查询去还原为具体的类型
func (v Value) Interface() (i interface{})

//Value 自身也提供丰富的方法,直接将 Value 转换为简单类型实例,如果类型不匹配,则直接引起 panic
func (v Value) Bool () bool
func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Uint() uint64

6) 从 Value 的指针到值

从一个指针类型的 Value 获得值类型 Value 有两种方法,示例如下。

//如果 v 类型是接口,则 Elem() 返回接口绑定的实例的 Value,如采 v 类型是指针,则返回指针值的 Value,否则引起 panic
func (v Value) Elem() Value
//如果 v 是指针,则返回指针值的 Value,否则返回 v 自身,该函数不会引起 panic
func Indirect(v Value) Value

7) Type 指针和值的相互转换

指针类型 Type 到值类型 Type。例如:

//t 必须是 Array、Chan、Map、Ptr、Slice,否则会引起 panic
//Elem 返回的是其内部元素的 Type
t.Elem() Type

值类型 Type 到指针类型 Type。例如:

//PtrTo 返回的是指向 t 的指针型 Type
func PtrTo(t Type) Type

8) Value 值的可修改性

Value 值的修改涉及如下两个方法:

//通过 CanSet 判断是否能修改
func (v Value ) CanSet() bool
//通过 Set 进行修改
func (v Value ) Set(x Value)

Value 值在什么情况下可以修改?我们知道实例对象传递给接口的是一个完全的值拷贝,如果调用反射的方法 reflect.ValueOf() 传进去的是一个值类型变量, 则获得的 Value 实际上是原对象的一个副本,这个 Value 是无论如何也不能被修改的。

如果传进去的是一个指针,虽然接口内部转换的也是指针的副本,但通过指针还是可以访问到最原始的对象,所以此种情况获得的 Value 是可以修改的。下面来看一个简单的示例。
package main
import (
    "fmt"
    "reflect"
)
type User struct {
    id int
    Name string
    Age int
}
func main() {
    u := User{Id: 1, Name:"andes", Age: 20}
    va := reflect.ValueOf(u)
    vb := reflect.ValueOf(&u)
    //值类型是可修改的
    fmt.Println(va.CanSet(), va.FieldByName("Name").CanSet()) //false false
    //指针类型是可修改的
    fmt.Println(vb.CanSet(), vb.Elem().FieldByName("Name").CanSet()) //false     false
    fmt.Printf("%v\n", vb)
    name :="shine"
    vc := reflect.ValueOf(name)

    //通过 Set 函数修改变量的值
    vb.Elem().FieldByName("Name").Set(vc)
    fmt.Printf("%v\n", vb)
}
运行结果:

false false
false true
&{1 andes 20)
&{1 shine 20)

这里归纳出了反射的三条定律:
  • 反射可以从接口值得到反射对象。
  • 反射可以从反射对象获得接口值。
  • 若要修改一个反射对象,则其值必须可以修改。