文末有源码下载链接!
如果说“Go语言类型系统”像人生的角色系统,那么反射就是你拿着一面可以“照见本质”的镜子,透过镜子,你能看到一个对象的真实身份、字段、方法,以及它隐藏的全部能力。
但镜子是神奇的,也是危险的:
-
用得好,是动态能力的利器;
-
用得不好,是性能噩梦、Bug温床、甚至让你的类型安全瞬间崩塌。
一、为什么需要反射?
在静态语言中,我们通常希望类型在编译器已知,这能让:
-
运行时安全
-
编译器优化更强
-
性能更高
但某些场景中,你必须在运行期处理一个“我不知道是什么类型”的对象,例如:
-
JSON反序列化
-
ORM映射数据库字段
-
配置解析
-
gRPC/RPC 框架自动绑定
-
单元测试工具
-
序列化框架
这些地方,你必须动态获取字段名、方法名、类型信息...
此时,反射就是你唯一的工具。
二、反射的核心三件套Type、Value、Kind
reflect.Type
reflect.Value
reflect.Kind
1. Type:类型信息
-
是静态类型的运行时表现形式
-
比如“string”、“int”、“struct”
2. Value:值信息
Value描述一个对象的真实值:
-
100
-
“hello”
-
struct{...}
3. Kind:底层分类
Kind不是类型,而是类型的底层归类:
-
int
-
float
-
slice
-
map
-
struct
三、反射的基本原理:接口+元数据
反射=从接口的运行时类型信息中,恢复出真实类型与值。
接口存两部分:
-
动态类型(type)
-
动态值(value)
反射只是把这俩拿出来,再给我们操作的能力。
t := reflect.TypeOf(x)
v := reflect.ValueOf(x)
四、反射的三大能力
Go的反射非常克制,也非常强大,分为三个层级:
1. 检查一个值
你可以:
-
看类型:TypeOf
-
看底层种类:Kind()
-
看字段:Field
-
看标签:Tag
-
看方法:NumMethod()
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func (u *User) SetName(name string) {
u.Name = name
fmt.Println("Name updated to", u.Name)
}
func TestT(t *testing.T) {
u := User{
ID: 1,
Name: "张三",
}
tp := reflect.TypeOf(u)
for i := 0; i < tp.NumField(); i++ {
f := tp.Field(i)
fmt.Println(f.Name, f.Tag.Get("json"))
}
}
你可以通过反射读取struct标签,这也是JSON/ORM能自动映射的原因
2. 修改一个值
你要用Elem()并传指针。
//修改name
v := reflect.ValueOf(&u).Elem()
v.FieldByName("Name").SetString("李四")
fmt.Println(u)
// 输出 {1 李四}
3. 调用方法(Call)
//调用Call
m := reflect.ValueOf(&u).MethodByName("SetName")
m.Call([]reflect.Value{reflect.ValueOf("王五")})
fmt.Println(u)
// 输出 {1 王五}
五、反射的危险与性能成本
反射是高成本、低安全性的,应谨慎使用。为什么?因为:
1. 反射是运行时的行为,慢
-
反射的速度大约是普通方法调用的5-50倍
-
内存分配增加
-
逃逸增多
原因:
-
类型判断逻辑复杂
-
必须保证类型安全
-
需要额外的内存结构
2. 缺乏编译期检查,容易犯错
FieldByName
"age"
// 字段写错也不会报错
3. 破坏类型系统,变成“动态语言”
很多新手滥用反射,让代码变得:
-
不稳定
-
不可读
-
不可维护
六、最佳实践
1. 能不用反射就不用
优先顺序:普通代码->接口->泛型->最后才是反射
2. 反射只用于框架层,而不是业务层
哪些地方应该用反射?
-
JSON、YAML解析
-
ORM框架
-
RPC框架
-
单元测试mock工具
-
数据绑定系统
3. 反射的结果要缓存
不要在循环中反复反射类型。
var
.Map
[reflect.Type][]
4. 用反射式非常严格的:必须先判断Kind
if v.Kind() != reflect.Struct {
return errors.New("must be struct")
}
七、实战,利用反射实现一个小型ORM标签解析器
type User struct {
ID int `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Age int `json:"age" db:"age"`
}
写一个函数,返回所有字段对应的数据库列:
func Parse(v interface{}) map[string]interface{} {
t := reflect.TypeOf(v)
val := reflect.ValueOf(v)
if t.Kind() == reflect.Ptr {
t = t.Elem()
val = val.Elem()
}
res := make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
tag := f.Tag.Get("db")
if tag == "" {
continue
}
res[tag] = val.Field(i).Interface()
}
return res
}
调用
u2 := User{
ID: 1,
Name: "张三",
Age: 10,
}
fmt.Println(Parse(u2))
// 输出 map[age:10 id:1 name:张三]
*源码地址*
1、公众号“Codee君”回复“每日一Go”获取源码
反射是一面“照见真相的镜子”,你可以看到一个对象的真实身份、能力和内部结构;但这面镜子沉重、昂贵、易碎,只有在真正必要的时候,你才应当举起它。
如果您喜欢这篇文章,请点赞、推荐+分享给更多朋友,万分感谢!