1.Golang中的反射如何使用
1
| golang 中的反射以reflect.ValueOf(i)和reflect.TypeOf(i)作为入口
|
1
2
3
4
5
6
7
| func test4() {
var s any = "zhang"
t := reflect.TypeOf(s)
v := reflect.ValueOf(s)
fmt.Printf("t: %v\n", t)
fmt.Printf("v: %v\n", v)
}
|
输出
1.1.reflect.ValueOf(i)
- reflect.ValueOf(i)返回的是reflect.Value类型,它封装了对i的运行时表示,包括它的具体值和类型
- 它不仅仅是一个具体值,而是一个可以动态访问和操作值的反射对象
- 通过v := reflect.Value,你可以:
- 获取变量的值(v.Int(),v.String())
- 获取变量的类型(v.Type())
- 调用方法(v.Method(int).Call(),v.MethodByName(string).Call())
- 修改值(如果传入的是指针且可寻址,v.Elem().Set())
- 获取字段值(对于结构体,v.NumField(),v.Field(j))
- v是与具体的示例i绑定的,它持有i中的值信息和类型信息
- 如果i是一个interface{},v会捕获接口中存储的动态值(如下)
1
2
3
4
5
6
7
8
9
10
11
| func test3() {
var s any = "zhang"
v := reflect.ValueOf(s)
fmt.Printf("v.Type(): %v\n", v.Type())
fmt.Printf("v: %v\n", v)
s = 3
v = reflect.ValueOf(s)
fmt.Printf("v.Type(): %v\n", v.Type())
fmt.Printf("v: %v\n", v)
}
|
输出
1
2
3
4
| v.Type(): string
v: zhang
v.Type(): int
v: 3
|
1.2.reflect.TypeOf(i)
- reflect.TypeOf(i)返回的是reflect.Type类型,表示i的类型的元数据
- 它只包含类型信息,不包含任何具体的值
- 通过t := reflect.Type,你可以:
- 获取类型名称(t.Name())
- 获取底层类型类别(t.Kind())
- 检查字段(对于结构体,例如t.NumField(),需要注意的是,这里只有字段的类型,没有字段的值)
- 检查方法(例如t.NumMethod(),t.Method())
- t是与i的类型绑定的,但它不持有i的具体值
- 它是一个静态的、抽象的表述,独立于任何示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| func test1() {
user := &User{
Name: "zhang",
Age: 26,
}
user1 := &User{
Name: "zhang3",
Age: 27,
}
v := reflect.ValueOf(user) //v
t := reflect.TypeOf(user) //t
fmt.Printf("v: %v\n", v)
fmt.Printf("t: %v\n", t)
v.Method(0).Call([]reflect.Value{reflect.ValueOf("world")}) //reflect.Value本来就已经与实例绑定,可以直接调用方法
v1 := reflect.ValueOf(user1)
t.Method(0).Func.Call([]reflect.Value{v1, reflect.ValueOf("world")}) //reflect.Type没有与任何实例绑定,因此在调用方法时需要手动提供接收者v1
}
|
输出
1
2
3
4
| v: &{zhang 26 {}}
t: *main.User
hello zhang , world
hello zhang3 , world
|
1.3.类比理解
- reflect.ValueOf(i): 像是一个装着具体物品(值)的盒子,你可以打开盒子取出物品(值),也可以看到盒子上写的标签(类型)。 例子:一个装着“42”的盒子,标签写着“int”。
- reflect.TypeOf(i): 像是一个物品的说明书,只告诉你物品的规格(类型信息),但手里没有实际的物品。 例子:一份“int 类型说明书”,告诉你这是整数类型,但没有具体的“42”。
2.反射的存在,有什么必要性
2.1.反射的核心价值
反射的核心价值在于运行时的动态性,对于一些框架和一些通用处理来说,有些需求需要在运行时处理未知或者动态的数据类型,而反射恰恰填补了这些空白
2.2.如果没有反射,哪些功能会变得难以实现
1
2
3
4
5
6
7
| json.Marshal()与json.Unmarshal()中,使用反射获取了结构体中的tags,并将其映射到json数据中。
开发者只需定义结构体和标签,即可实现序列化与反序列化
如果没有反射,你就需要为每种结构体手动编写Marshal()和Unmarshal()函数,逐个字段处理。
并且,你也无法编写通用的序列化库,必须针对每种类型写特定实现,违背DRY(Don't Repeat Yourself)原则
|
1
2
3
4
5
| ORM框架通过反射分析结构体字段,将其映射到数据库表字段,反射动态生成SQL。
也可以通过反射构造建表语句。
如果没有反射,你需要为每个模型手动编写CRUD操作的SQL语句;你也无法实现通用的数据库操作函数,每次新增模型或修改字段都需要重写代码
|
1
2
3
| 依赖注入框架通过反射动态分析类型间的依赖关系,在运行时构造对象
如果没有反射,你需要手动注入依赖关系,对于大型项目来说,依赖关系维护很繁琐也很容易出错
|
1
| 通过反射可以动态调用方法,即使在编译时不知道具体类型,这在插件系统和RPC系统中非常有用
|
1
2
3
| golang中interface{}可以持有任意类型,反射是处理这种动态类型的标准方式。
如果没有反射,你只有通过类型断言逐一尝试已知类型,无法处理未知类型
|
1
| 反射支持根据运行时配置动态加载和调用代码。例如,Web框架(如gin)通过反射解析路由和处理函数
|
3.反射的代价与权衡
尽管反射很有必要,但它也有缺点
- 性能开销:反射比直接调用慢,因为设计运行时检查和动态分配
- 代码安全性:反射无法通过编译器检查代码,容易引发运行时错误
- 复杂性:反射在代码可读性上和维护上都比较差
因此,反射通常用于初始化阶段和低频操作,而不是高性能路径
4.总结:反射的必要性
反射的存在必要性在于它提供了运行时的灵活性和通用型,解决了静态语言在面对动态需求时的局限
如果没有反射:
- 通用库(JSON,ORM)难以实现
- 动态行为(插件、方法调用)难以实现
因此,我们可以在一些动态需求的实现上考虑使用反射。但是在需要高性能的场景上,应尽量避免反射的使用