Go语言反射的3个定律(附带实例)
反射三定律是建立在反射和接口的关系之上的。在 Go 语言中,接口变量能够被多种类型的变量赋值,这让接口变量看起来像是动态类型的。但是,Go 语言是静态类型的编程语言,因此接口变量是静态类型的。
明确了以上内容后,下面将依次讲解 Go 语言的反射三定律。
例如,定义值为 7 的 int 类型变量 i,声明 bool 型变量 b,定义值为 11 的 float64 类型变量 f,先使用 reflect.TypeOf() 函数分别打印变量 i、b 和 f 的类型及其反射类型;再使用 reflect.ValueOf() 函数分别打印这3个变量的值及其反射类型。代码如下:
这里有一个问题,在上述代码中,没有接口变量;那么,为什么说“成功地把接口变量转换为反射对象”了呢?这是因为在 reflect.TypeOf() 和 reflect.ValueOf() 两个函数里都包含一个空接口。
TypeOf() 函数的语法格式如下:
ValueOf() 函数的语法格式如下:
Interface() 函数的语法格式如下:
例如编写一个程序,实现 3 个功能:
代码如下:
注意,在修改反射对象的值之前,要使用 CanSet() 函数确认反射对象具有“可写性”。当反射对象具有“可写性”时,CanSet() 函数返回 true;否则,CanSet() 函数返回 false。
例如编写一个程序,定义值为 3.1415926 的 float64 类型变量,使用 reflect.ValueOf() 函数获取变量值的反射类型后,使用 SetFloat() 函数把反射对象的值修改为 3.14。代码如下:
那么,上述代码为什么会抛出异常呢?为了一探究竟,需要使用 CanSet() 函数验证反射对象 valueF 是否具有“可写性”。代码如下:
那么,如何才能让反射对象 valueF 具有“可写性”呢?为了解决这个问题需要执行如下的两个步骤:
明确解决问题的方法后,下面通过编码修改反射对象的值。代码如下:
明确了以上内容后,下面将依次讲解 Go 语言的反射三定律。
Go语言接口变量转反射对象
使用 reflect 包提供的 reflect.TypeOf() 和 reflect.ValueOf() 两个函数,可以把接口变量转换为反射对象。例如,定义值为 7 的 int 类型变量 i,声明 bool 型变量 b,定义值为 11 的 float64 类型变量 f,先使用 reflect.TypeOf() 函数分别打印变量 i、b 和 f 的类型及其反射类型;再使用 reflect.ValueOf() 函数分别打印这3个变量的值及其反射类型。代码如下:
package main import ( "fmt" "reflect" ) func main() { var i int = 7 //定义 int 类型变量 i typeI := reflect.TypeOf(i) //获取变量 i 的类型的反射类型 valueI := reflect.ValueOf(i) //获取变量 i 的值的反射类型 fmt.Printf("变量 i 的数据类型:%v,反射类型:%T\n", typeI, typeI) //打印变量 i 的类型及其反射类型 fmt.Printf("变量 i 的值:%v,反射类型:%T\n", valueI, valueI) //打印变量 i 的值及其反射类型 var b bool //声明 bool 类型变量 b typeB := reflect.TypeOf(b) //获取变量 b 的类型的反射类型 valueB := reflect.ValueOf(b) //获取变量 b 的值的反射类型 fmt.Printf("变量 b 的数据类型:%v,反射类型:%T\n", typeB, typeB) //打印变量 b 的类型及其反射类型 fmt.Printf("变量 b 的值:%v,反射类型:%T\n", valueB, valueB) //打印变量 b 的值及其反射类型 var f float64 = 11 //定义 float64 类型变量 f typeF := reflect.TypeOf(f) //获取变量 f 的类型的反射类型 valueF := reflect.ValueOf(f) //获取变量 f 的值的反射类型 fmt.Printf("变量 f 的数据类型:%v,反射类型:%T\n", typeF, typeF) //打印变量 f 的类型及其反射类型 fmt.Printf("变量 f 的值:%v,反射类型:%T\n", valueF, valueF) //打印变量 f 的值及其反射类型 }运行结果如下:
变量 i 的数据类型:int,反射类型:*reflect.rtype
变量 i 的值:7,反射类型:reflect.Value
变量 b 的数据类型:bool,反射类型:*reflect.rtype
变量 b 的值:false,反射类型:reflect.Value
变量 f 的数据类型:float64,反射类型:*reflect.rtype
变量 f 的值:11,反射类型:reflect.Value
这里有一个问题,在上述代码中,没有接口变量;那么,为什么说“成功地把接口变量转换为反射对象”了呢?这是因为在 reflect.TypeOf() 和 reflect.ValueOf() 两个函数里都包含一个空接口。
TypeOf() 函数的语法格式如下:
func TypeOf(i interface{}) Type
ValueOf() 函数的语法格式如下:
func ValueOf(i interface{}) Value不难发现,reflect.TypeOf() 和 reflect.ValueOf() 两个函数接收的是空接口类型的变量。当调用 reflect.TypeOf() 和 reflect.ValueOf() 两个函数处理一个变量时,这个变量被存储在一个空接口类型的变量中。因为 Go 语言传参的方式是值传递,所以这个变量的数据类型将隐式地转换为空接口类型。
Go语言反射对象转接口变量
接口变量和反射对象是可以相互转换的。那么,如何把反射对象转换为接口变量呢?这时就需要通过 reflect.ValueOf() 函数调用 Interface() 函数实现。Interface() 函数的语法格式如下:
func (v Value) Interface() (i interface{}) { return valueInterface(v, true) }Interface() 函数的工作原理如下:一个反射类型是 reflect.Value 的对象经 Interface() 函数处理后,这个反射对象的类型和值将被存储在一个接口变量中,并且这个接口变量将作为 Interface() 函数的返回值。这样就可以成功地把反射对象转换为接口变量。
例如编写一个程序,实现 3 个功能:
- 定义值为 3.1415926 的接口变量 i,打印接口变量的值及其数据类型;
- 使用 reflect.ValueOf() 函数获取接口变量的值,并打印接口变量的值的反射类型;
- 通过 reflect.ValueOf() 函数调用 Interface() 函数,把反射对象转换为接口变量后,打印接口变量的值及其数据类型。
代码如下:
package main import ( "fmt" "reflect" ) func main() { var i interface{} = 3.1415926 //定义接口变量 fmt.Printf("接口变量 i 的值:%v,类型:%T\n", i, i) //打印接口变量 i 的值及其类型 valueI := reflect.ValueOf(i) //获取接口变量 i 的值的反射类型 //打印接口变量 i 的值的反射类型 fmt.Printf("接口变量转换为反射对象后,接口变量 i 的值的反射类型:%T\n", valueI) res := valueI.Interface() //把反射对象转换为接口变量 //打印接口变量 res 的值及其类型 fmt.Printf("反射对象转换为接口变量后,接口变量 res 的值:%v,类型:%T\n", res, res) }运行结果如下:
接口变量 i 的值:3.1415926,类型:float64
接口变量转换为反射对象后,接口变量 i 的值的反射类型:reflect.Value
反射对象转换为接口变量后,接口变量 res 的值:3.1415926,类型:float64
Go语言修改反射对象的值
为了修改反射对象的值,可以使用 CanSet() 和 SetFloat() 等函数。注意,在修改反射对象的值之前,要使用 CanSet() 函数确认反射对象具有“可写性”。当反射对象具有“可写性”时,CanSet() 函数返回 true;否则,CanSet() 函数返回 false。
例如编写一个程序,定义值为 3.1415926 的 float64 类型变量,使用 reflect.ValueOf() 函数获取变量值的反射类型后,使用 SetFloat() 函数把反射对象的值修改为 3.14。代码如下:
package main import ( "reflect" ) func main() { var f float64 = 3.1415926 //定义 float64 类型的变量 f valueF := reflect.ValueOf(f) //获取变量 f 的值的反射类型 valueF.SetFloat(3.14) //把反射对象的值修改为 3.14 }运行上述代码,抛出如下异常:
panic: reflect: reflect.Value.SetFloat using unaddressable value
那么,上述代码为什么会抛出异常呢?为了一探究竟,需要使用 CanSet() 函数验证反射对象 valueF 是否具有“可写性”。代码如下:
package main import ( "fmt" "reflect" ) func main() { var f float64 = 3.1415926 //定义 float64 类型的变量 f valueF := reflect.ValueOf(f) //通过获取变量 f 的值的反射类型,创建反射对象 valueF fmt.Println("反射对象 valueF 是否具有“可写性”:", valueF.CanSet()) }运行结果如下:
反射对象 valueF 是否具有“可写性”: false
运行结果显示反射对象 valueF 不具有“可写性”。那么,如何才能让反射对象 valueF 具有“可写性”呢?为了解决这个问题需要执行如下的两个步骤:
- 创建反射对象时,传入变量的指针;
- 因为要修改的是指针指向的数据,所以要通过 reflect.ValueOf() 函数调用 Elem() 函数获取指针指向的数据。
明确解决问题的方法后,下面通过编码修改反射对象的值。代码如下:
package main import ( "fmt" "reflect" ) func main() { var f float64 = 3.1415926 //定义 float64 类型的变量 f valueF := reflect.ValueOf(&f).Elem() //获取指针指向的反射对象的值 //打印反射对象 valueF 是否具有“可写性” fmt.Println("反射对象 valueF 是否具有“可写性”:", valueF.CanSet()) fmt.Println("反射对象 valueF 的初始值:", valueF) //打印反射对象 valueF 的初始值 valueF.SetFloat(3.14) //把反射对象的值修改为 3.14 fmt.Println("反射对象 valueF 的初始值被修改为:", valueF) //打印反射对象 valueF 修改后的值 }运行结果如下:
反射对象valueF是否具有“可写性”: true
反射对象valueF的初始值: 3.1415926
反射对象valueF的初始值被修改为: 3.14