之前在 ​​Go 语言系列33:静态类型与动态类型​​​ 中讲过,一个接口变量,实际上都是由一 ​​pair​​ 对(type 和 data)组合而成,pair 对中记录着实际变量的值和类型。也就是说在真实世界(反射前环境)里,type 和 value 是合并在一起组成接口变量的。

而在反射的世界(反射后的环境)里,type 和 data 却是分开的,他们分别由 ​​reflect.Type​​​ 和 ​​reflect.Value​​ 来表现。

Go 语言里有反射三定律,是你在学习反射时,很重要的参考:

  1. Reflection goes from interface value to reflection object.
  2. Reflection goes from reflection object to interface value.
  3. To modify a reflection object, the value must be settable.

接下来我们就来讲一讲反射三大定律。


Go 语言系列38:反射三大定律_类型变量

反射第一定律

Go 语言系列38:反射三大定律_对象类型_02


Reflection goes from interface value to reflection object.

反射第一定律:反射可以将“接口类型变量”转换为“反射类型对象”。

这里反射类型指 ​​reflect.Type​​​ 和 ​​reflect.Value​​ 。

通过之前我们讲过的 ​​reflect.TypeOf()​​​ 方法和 ​​reflect.ValueOf()​​ 方法可以分别获得接口值的类型和接口值的值。这两个方法返回的对象,我们称之为反射对象。

package main

import (
"fmt"
"reflect"
)

func main() {
var a interface{} = 3.14
fmt.Printf("接口变量的类型为 %T ,值为 %v\n", a, a)
t := reflect.TypeOf(a)
v := reflect.ValueOf(a)
fmt.Printf("从接口变量到反射对象:Type对象类型为 %T\n", t)
fmt.Printf("从接口变量到反射对象:Value对象类型为 %T\n", v)
}

运行该程序输出结果如下:

接口变量的类型为 float64 ,值为 3.14
从接口变量到反射对象:Type对象类型为 *reflect.rtype
从接口变量到反射对象:Value对象类型为 reflect.Value

可以看到,使用 ​​reflect.TypeOf()​​​ 和 ​​reflect.ValueOf()​​​ 方法完成了从接口类型变量到反射对象的转换。在这里说接口类型是因为 ​​TypeOf​​​ 和 ​​ValueOf​​​ 两个函数接收的是 ​​interface{}​​ 空接口类型, Go 语言函数都是值传递,会将类型隐式转换成接口类型。


Go 语言系列38:反射三大定律_类型变量

反射第二定律

Go 语言系列38:反射三大定律_对象类型_02


Reflection goes from reflection object to interface value.

反射第二定律:反射可以将“反射类型对象”转换为“接口类型变量”

第二定律刚好和第一定律相反,第一定律讲的是从接口变量到反射对象的转换,而第二定律讲的是从反射对象到接口变量的转换。

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

其函数声明如下:

// Interface returns v's current value as an interface{}.
// It is equivalent to:
// var i interface{} = (v's underlying value)
// It panics if the Value was obtained by accessing
// unexported struct fields.
func (v Value) Interface() (i interface{}) {
return valueInterface(v, true)
}

最后转换后的对象静态类型为 ​​interface{}​​,我们可以使用类型断言转换为原始类型。

package main

import (
"fmt"
"reflect"
)

func main() {
var a interface{} = 3.14

fmt.Printf("接口变量的类型为 %T ,值为 %v\n", a, a)

t := reflect.TypeOf(a)
v := reflect.ValueOf(a)

// 反射第一定律
fmt.Printf("从接口变量到反射对象:Type对象类型为 %T\n", t)
fmt.Printf("从接口变量到反射对象:Value对象类型为 %T\n", v)

// 反射第二定律
i := v.Interface()
fmt.Printf("从反射对象到接口变量:对象类型为 %T,值为 %v\n", i, i)
// 使用类型断言进行转换
x := v.Interface().(float64)
fmt.Printf("x 类型为 %T,值为 %v\n", x, x)
}

运行该程序输出结果如下:

接口变量的类型为 float64 ,值为 3.14
从接口变量到反射对象:Type对象类型为 *reflect.rtype
从接口变量到反射对象:Value对象类型为 reflect.Value
从反射对象到接口变量:对象类型为 float64,值为 3.14
x 类型为 float64,值为 3.14

Go 语言系列38:反射三大定律_类型变量

反射第三定律

Go 语言系列38:反射三大定律_对象类型_02


To modify a reflection object, the value must be settable.

反射第三定律:如果要修改“反射类型对象”其值必须是“可写的”

我们首先来看一看下面这段代码:

package main

import "reflect"

func main() {
var a float64 = 3.14
v := reflect.ValueOf(a)
v.SetFloat(2.1)
}

运行该代码段将会抛出异常:

panic: reflect: reflect.Value.SetFloat using unaddressable value

这里你可能会疑惑,为什么这里会抛出寻址的异常,其实是因为这里的变量 ​​v​​​ 是“不可写的”。​​settable​​(“可写性”)是反射类型变量的一个属性,但也不是说所有的反射类型变量都有这个属性。

要想知道一个 ​​reflect.Value​​​ 类型变量的“可写性”,我们可以使用 ​​CanSet​​ 方法来进行检查:

package main

import (
"fmt"
"reflect"
)

func main() {
var a float64 = 3.14
v := reflect.ValueOf(a)
fmt.Println("是否可写:", v.CanSet())
}

运行该程序输出结果如下:

是否可写: false

可以看到,我们这个变量 ​​v​​​ 是不可写的。对于一个不可写的变量,使用 ​​Set​​​ 方法会报错。这里实质上还是 Go 语言里的函数都是值传递问题,想象一下这里传递给 ​​reflect.ValueOf​​​ 函数的是变量 ​​a​​​ 的一个拷贝,而非 ​​a​​​ 本身,所以如果对反射对象进行更新,其原始变量 ​​a​​ 根本不会受到影响,所以是不合法的,“可写性”就是为了避免这个问题而设计出来的。

所以,要让反射对象具备“可写性”,一定要注意创建反射对象时要传入变量的指针,于是乎我们修改代码如下:

package main

import (
"fmt"
"reflect"
)

func main() {
var a float64 = 3.14
v := reflect.ValueOf(&a)
fmt.Println("是否可写:", v.CanSet())
}

但运行该程序还是会输出不可写:

是否可写: false

因为事实上我们这里要修改的是该指针指向的数据,使用还要使用 ​​Value​​​ 类型的 ​​Elem()​​ 方法,对指针进行“解引用”,该方法返回指针指向的数据。

package main

import (
"fmt"
"reflect"
)

func main() {
var a float64 = 3.14
v := reflect.ValueOf(&a).Elem()
fmt.Println("是否可写:", v.CanSet())

v.SetFloat(2)
fmt.Println(v)
}

运行该程序输出如下:

是否可写: true
2


参考文献:

[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序设计语言, Translated by 李道兵, 高博, 庞向才, 金鑫鑫 and 林齐斌, 机械工业出版社, 2017.