反射
反射是什么?
- 反射是运行时检查自身结构的机制
- 反射是困惑的源泉。
反射特性与 interface 紧密相关。
接口
1. 类型
Go是静态类型语言,比如int、float32、[]byte,等等。每个变量都有一个静态类型,而且在编译时就确定了。
考虑如下几类变量声明:
type Myint int
var i int
var j Myint
变量 i 和 j 是相同的类型吗?不是的,二者拥有不同的静态类型,尽管二者的底层类型都是 int ,但在没有类型转换的情况下是不可以相互赋值的。
Go提供了布尔、数值和字符串类型的基础类型,还有一些使用这些基本类型组合的复合类型,比如数组、结构体、指针、切片、map和 channel 等。interface 也可以称为一种复合类型。
2. interface 类型
每个 interface
类型代表一个特性的方法集,方法集中的方法称为接口。比如:
type Animal interface {
Speak() string
}
Animal 就是一个接口类型,其包含一个 Speak() 方法
1)interface变量
就像任何其他类型一样,我们也可以声明 interface
类型的变量。比如:
var animal Animal
上面的 animal 变量的值为 nil,它没有赋值,它可以存储什么值呢?
2)实现接口
任何类型只要实现了 interface
类型的所有方法,就可以声称该类型实现了这个接口,该类型的变量就可以存储到 interface
变量中。
比如结构体 Cat 实现了Speak() 方法:
type Cat struct {
}
func (c Cat) Speak() string {
return "Meow"
}
结构体Cat的变量就可以存储到 animal 变量中:
var animal Animal
var cat Cat
animal = cat
interface
变量可以存储到任意实现了该接口类型的变量。
3)复合类型
为什么 interface
变量可以存储任意实现了该类型接口的变量。
因为 interface
类型的变量在存储某个变量时会同时保存变量类型和变量值。
type iface struct {
tab *itab // 保存变量类型
data unsafe.Pointer // 变量值位于堆栈的指针
}
Go的反射就是在运行时操作 interface 中的值和类型的特性。
反射定理
interface 类型有一个(value、type)对,而反射就是操纵 interface 的这个(value,type)对的机制。具体一点说就是Go提供一组方式来获取 interface 的value 和 type。
1. reflect包
reflect包中提供了reflect.Type
和reflect.Value
两个类型,分别代表 interface 中的类型和value。
reflect包中同时提供两个方法来获取 interface 的 value 类型:
func ValueOf(i interface{}) Value
func TypeOf(i interface{}) Type
具体关系如下图所示:
在下面的描述中,我们称reflect.Type 和reflect.Value 为 interface 的反射对象
2. 反射定律
1)第一定律:反射可以将 interface 类型变量转换成反射对象
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
t := reflect.TypeOf(x)
fmt.Println("type:", t)
v := reflect.ValueOf(x)
fmt.Println("value:", v)
}
程序输出如下:
type: float64
value: 3.4
在上面的例子中,好像没有 interface 变量,实则不然,变量 x 在传入 reflect.TypeOf()函数时,实际上做了一次转化,x变量被转换成一个空接口传入,reflect.ValueOf(x)也是如此。该例子展示了反射的一个能力,即可以获取 interface 变量的类型和值,这是反射进一步操纵 interface变量的基础。
2)第二定律:反射可以将反射对象还原成 interface 对象
之所以叫“反射”,是因为反射对象与 interface 对象时可以相互转换的。
func foo() {
var A interface{}
A = 100
v := reflect.ValueOf(A)
B := v.Interface()
if A == B {
fmt.Println("A==B")
} else {
fmt.Println("A!=B")
}
}
在上面的函数中,通过 reflect.ValueOf()获取接口变量A的反射对象,然后又通过反射对象的interface()获取B,结果A和B是相同的。
3)第三定律:反射对象可修改,value值必须是可设置的
通过反射可以将 interface 类型的变量转换成反射对象,可以使用该反射对象设置 interface 变量持有的值。
我们可以通过 reflect.Value 的一系列 SetXXX() 方法来设置反射对象的值。先看一个失败的例子:
package main
import "reflect"
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
}
在上面的代码中,通过反射对象v设置新值,会触发panic。报错如下:
panic: reflect: reflect.Value.SetFloat using unaddressable value
错误原因是v是不可修改的,为什么会如此呢?
反射对象是否可修改取决于其所存储的值。上例中传入 reflect.ValueOf()函数的其实是x的值,而非x本身,即通过v修改其值是无法影响x的,也就是无效的修改,所以会报错。
如果构建v时使用x的地址就可实现修改了,但此时v代表的是指针地址,我们要设置的是指针所指向的内容,即我们想要修改*v。那怎么通过v修改x的值呢?
reflect.Value() 提供了Elem() 方法,可以获得指向 value 的指针。修正后的代码如下:
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(&x)
v.Elem().SetFloat(7.1)
fmt.Println("x:"x)
}
输出如下:
x: 7.1