反射
与其他语言一样,Go语言的反射同样是指,计算机程序在运行时,可以访问、检测和修改它本身状态或行为的一种能力。
其在reflect包里,定义了一个接口和一个结构体,即reflect.Type接口与reflect.Value结构体,它们提供了很多函数来获取存储在接口里的类型信息。
reflect.Type接口:主要提供关于类型相关的信息
reflect.Value结构体:主要提供关于值相关的信息,可以获取甚至改变类型的值。
reflect包中提供了两个基础的关于反射的函数,用来获取上述接口与结构体:
func TypeOf(i interface()) Type func ValueOf(I interface()) Value
其中:
TypeOf()函数:用来提取一个接口中值的类型信息。由于它的输入参数是一个空的interface{},所以在调用此函数时,实参会先被转换为interface{}类型。这样,实参的类型信息、方法集、值信息都存储到interface{}变量里了。
ValueOf()函数:返回一个结构体变量,包含类型信息及实际值。
详细原理图如下:
反射的3大原则
在Go语言中,反射有3大原则:
反射可以将”接口类型变量“转换为”反射类型对象“
反射可以将”反射类型对象“转换为”接口类型变量“
如果要修改”反射类型对象“,则其值必须是“可写的”
“接口类型变量”转换为“反射类型对象”
反射是一种检查存储在接口变量中的类型与值对的机制。reflect包中的两个类型:Type和Value。这2种类型给了我们访问一个接口变量种所包含的内容的途径。
另外,2个简单的函数reflect.TypeOf()和reflect.ValueOf()可以检索一个接口值的reflect.Type与reflect.Value部分。示例如下:
import ( "fmt" "reflect" ) func main() { var x float64 = -3.151592653 fmt.Println("Type:", reflect.TypeOf(x)) v := reflect.ValueOf(x) fmt.Println("Value:", v) fmt.Println("Type:", v.Type()) fmt.Println("is float64:", v.Kind() == reflect.Float64) fmt.Println("Value:", v.Float()) }
运行之后,我们会得到如下结果。
上面代码,我们先是定义了一个float64的变量,然后将其赋值给reflect.TypeOf(x)函数。在我们调用该函数时,x会被保存到空接口中,然后这个空接口作为参数传递,reflect.TypeOf会将空接口拆包恢复出类型信息。
当然,reflect.ValueOf(x)同样也可以将值恢复出来,而且reflect.ValueOf获取的变量类型还可以使用方法进行操作。比如获取类型,值,以及对比类型。
“反射类型对象”转换为“接口类型变量”
这个与上面的正好相反,和物理学类似,有物质就有反物质。
在Go语言中,反射也能创造自己反面类型的对象。根据reflect.Value类型的变量,可以使用interface()方法恢复其接口类型的值。而且该方法,会把type和value信息打包并填充到一个接口变量中,然后返回。定义如下:
//定义 func (v Value) Interface() interface{} //例子 func main() { var name interface{}="liyuanjing" x :=reflect.TypeOf(name) y :=reflect.ValueOf(name) //从接口变量到反射对象 fmt.Printf("从接口变量到反射对象:Type对象的类型为%T \n",x) fmt.Printf("从接口变量到反射对象:Value对象的类型为%T \n",y) //从反射对象到接口变量 z :=y.Interface() fmt.Printf("从反射对象到接口变量:新对象的类型为%T 值为%v \n",z,z) }
运行之后,控制台输出效果如下:
“反射类型对象”修改(值必“可写的”)
在使用reflect.TypeOf()函数和reflect.ValueOf()函数时,如果传递的不是接口变量的指针,则反射世界里的变量始终只是真实世界里的一个复制,对该反射对象进行修改,并不能反映到真实世界里。
需要注意的是:
不是接收变量指针创建的反射对象,是不具备“可写性”的
是否具备“可写性”,可使用CanSet()方法来判断
对不具备“可写性”的对象进行修改,是没有意义的,也认为是不合法的,因此会报错。
如果需要让反射具备可写性,需要这样:
创建反射对象时,传入变量的是指针
使用Elem()方法,返回指针指向的数据。
判断可写性示例:
func main() { var name string = "liyuanjing" x := reflect.ValueOf(&name) fmt.Println("x的可写性为:", x.CanSet()) y := x.Elem() fmt.Println("y的可写性为:", y.CanSet()) }
运行之后,你会发现x是不可写的,y因为使用了Elem()方法,是可写的。
知道了如何使用反射世界里的对象具有可写性后,接下来是时候了解一下,如何对修改更新对象了。
在反射的Value对象中,有多个以单词Set开头的方法用于重新设置对应类型的值。比如:
func (v Value) SetBool(x bool) func (v Value) SetBytes(x []byte) func (v Value) setRunes(x []rune) func (v Value) SetComplex(x complex128) func (v Value) SetFloat(x float64) func (v Value) SetInt(x int64)
这些方法全部都是修改值的入口,比如,通过反射对象SetInt()方法进行更新值的示例如下:
func main() { var num int = 30 fmt.Println("原始值为:", num) x := reflect.ValueOf(&num) y := x.Elem() y.SetInt(500) fmt.Println("通过反射对象进行更新后,num的真实值为:", num) }
运行之后,效果如下: