首页 > 编程笔记 > Go语言笔记 阅读:8

Go语言反射的3个定律(附带实例)

反射三定律是建立在反射和接口的关系之上的。在 Go 语言中,接口变量能够被多种类型的变量赋值,这让接口变量看起来像是动态类型的。但是,Go 语言是静态类型的编程语言,因此接口变量是静态类型的。

明确了以上内容后,下面将依次讲解 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

因为 Go 语言把 reflect.TypeOf() 和 reflect.ValueOf() 两个函数返回的对象称作反射对象,所以通过上述代码就成功地把接口变量转换为反射对象。

这里有一个问题,在上述代码中,没有接口变量;那么,为什么说“成功地把接口变量转换为反射对象”了呢?这是因为在 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 个功能:
代码如下:
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 具有“可写性”呢?为了解决这个问题需要执行如下的两个步骤:
明确解决问题的方法后,下面通过编码修改反射对象的值。代码如下:
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

相关文章