反射(Reflection)是计算机科学中一种能够在运行时检查、访问和修改程序结构的能力。它允许程序在执行期间获取自身的元数据(metadata)信息,并且可以动态地操作和调用类、方法、属性等。理解 Go 反射的原理对于编写灵活、可扩展和高级的 Go 程序至关重要
反射类型 #
Go 语言是一种静态类型语言,这意味着在编译时就已经确定了变量的类型。如:
type myInt int
var a myInt
var b int
在这个例子中,a 和 b 就拥有了不同的静态类型,如果不进行类型转换,就不能互相赋值
反射的核心原理就是检查和操作变量的类型、值,Go 的反射通过 reflect.TypeOf()
、reflect.ValueOf()
来返回类型和值的相关信息,它们的返回值 reflect.Type
、reflect.Value
是实现反射的核心。
三大法则 #
可以将 interface{}
变量转换成反射对象
#
Reflection goes from interface value to reflection object.
当我们执行 reflect.ValueOf(1)
时,虽然看起来是获取了基本类型 int 对应的反射类型,但是由于 reflect.TypeOf
、reflect.ValueOf
两个方法的入参都是 interface{}
类型,所以在方法执行的过程中发生了类型转换。它经历了 int
-> interface{}
-> reflect.Value
转换过程。
从反射对象可以获取 interface{}
变量
#
Reflection goes from reflection object to interface value.
既然能够将接口类型的变量转换成反射对象,那么一定需要其他方法将反射对象还原成接口类型的变量,reflect 中的 reflect.Value.Interface 就能完成这项工作。
不过调用 reflect.Value.Interface
方法只能获得 interface{}
类型的变量,如果想要将其还原成最原始的状态还需要经过如下所示的显式类型转换:
v := reflect.ValueOf(1)
v.Interface().(int)
经历了如下两次类型转换:
如果要修改反射对象,那么反射对象必须是可设置的(CanSet)。 #
To modify a reflection object, the value must be settable.
如果我们想要更新一个 reflect.Value
,那么它持有的值一定是可以被更新的。
如下代码会报错:
i := 1
v := reflect.ValueOf(i)
v.SetInt(10)
// panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
正确更新一个 reflect.Value
实际上需要经过以下几个步骤:
- 调用
reflect.ValueOf
获取变量指针; - 调用
reflect.Value.Elem
获取指针指向的变量;Elem
方法可以获取到真正的对象 - 调用
reflect.Value.SetInt
更新变量的值。
即:
i := 1
v := reflect.ValueOf(&i)
v.Elem().SetInt(10)
应用场景以及弊端 #
应用场景 #
-
IDE 代码自动补全
-
对象序列化 ( JSON Marshal & Unmarshal)
-
fmt 相关函数的实现
-
Object Relational Mapping, 对象关系映射
弊端 #
-
增加代码复杂性
-
存在性能问题
-
无法在编译时进行类型检查,有一定安全问题