反射:程序在运行时照镜子,以及它的三条铁律
反射:程序在运行时照镜子,以及它的三条铁律
这一章讲 Go 的「元编程」入口:反射(reflection)。 平时类型在编译时就定死了;但「一个函数收任意类型」时(空接口), 你只能在运行时问:这到底是什么类型?里面有什么字段?值是多少? 读完你会用 reflect 包回答这三个问题,并记住三条铁律—— 尤其第三条:反射拿到的值,默认不许改。
1. 为什么要照镜子
书里给的使用场景:编译时不知道类型,运行时才需要搞清楚1。
具体例子:你有一个切片,想把它里的元素取出来当 map 的键—— map 的键类型写在声明里,可切片的元素类型运行时才确定(比如它是从 空接口传来的)。要造这种「对任意类型都成立」的代码,就得在运行时问类型—— 这就是反射的登场理由2。
书里同时给了使用边界,值得原样接住: 反射该用在通用编程和框架里;性能关键代码、类型安全要紧的地方,躲开它—— 它有性能开销、削弱类型安全、还让代码难维护3。 (JSON 库、ORM、依赖注入框架,底层全是反射;普通业务代码,几乎用不到。)
2. 主线例子:一个 student 的体检报告
本章主线,照原书的 stu := student{"Akansha", 17, 40000.565},
把 reflect 的核心工具挨个走一遍。
第一问:什么类型?reflect.TypeOf。
stu := student{"Akansha", 17, 40000.565}
stu_type := reflect.TypeOf(stu)
// → main.student 类型的全名(包名.类型名)
stu_type.Name()
// → student 类型的短名
reflect.TypeOf(stu_type)
// → *reflect.rtype 「类型本身的类型」——TypeOf 返回的是 reflect 包内部的 rtypeTypeOf 吃空接口、吐类型的描述对象4。
第二问:什么值?reflect.ValueOf。
reflect.ValueOf(stu)
// → {Akansha 17 40000.566} 打印出来和原值一样ValueOf 返回的是 reflect 包里的「值包装器」,不是原值本身——
要拿回原值,得用三定律之二的方法(见第 4 节)5。
第三问:源头类别是什么?Kind。
stu_type.Kind()
// → struct书里把 Type 和 Kind 的区别讲得很准:
Type 是你定义的那个类型(student),Kind 是这个类型的「源头类别」(struct)——
student 的源头是结构体,int64 的源头是整数,type MyInt int 的 Type 是 MyInt、
Kind 是 int6。反射分情况处理(「是结构体就拆字段」「是指针就进屋」)时,
看的是 Kind。
第四问:结构体里有几个字段、各是什么值?NumField 和 Field。
stu_value := reflect.ValueOf(stu)
num := stu_value.NumField() // → 3(注意:作用在 Value 上,不是 Type)
for i := 0; i < num; i++ {
fmt.Printf("第 %d 个字段:%v\n", i+1, stu_value.Field(i))
}
// → 第 1 个字段:Akansha
// → 第 2 个字段:17
// → 第 3 个字段:40000.566Field(i) 按声明顺序取第 i 个字段的值7。
第五问:把反射值还原成具体类型?String() / Int() / Float()。
name := reflect.ValueOf(stu.name).String() // → "Akansha"(string)
age := reflect.ValueOf(stu.age).Int() // → 17(int64!)
income := reflect.ValueOf(stu.income).Float() // → 40000.56640625(float64!)两个细节书里用输出点破了:
- Int() 返回 int64,Float() 返回 float64——不管原值是 int 还是 float32,
出口统一加宽;float32 的 40000.565 以 float64 打印就变成了
40000.56640625(浮点精度的老熟人,第 14 章的-3.3000002在这里又见面了)8; - 用错方法会 panic(对字符串调
.Int()之类),Kind 就是用来防这个的。
3. 反射三定律
书里以「定律」的规格给出三条,这是理解 reflect 包的总钥匙9:
定律一:反射从「接口值」走向「反射对象」。
TypeOf / ValueOf 吃进去的都是空接口(第 12 章那个能装任何值的盒子),
吐出来的是 reflect 包的 Type 和 Value 对象。
方向:接口 → 反射对象。 第一、二问的全部演示都是这条9。
定律二:反射从「反射对象」走回「接口值」。
value.Interface() 把反射值还原成空接口:
interface_val := value.Interface()
// → {Akansha 17 40000.566} 原值回来了
reflect.TypeOf(interface_val).Kind()
// → struct 还是原来那个类型方向:反射对象 → 接口。 两条定律合起来,反射和正常代码之间是双向互通的10。
定律三:要修改反射对象,值必须是「可设置」的。 书里用一个 panic 把这条讲透了:
var str string = "Hello"
val := reflect.ValueOf(str)
val.SetString("World")
// → panic: reflect.Value.SetString using unaddressable value为什么?ValueOf(str) 拿到的是 str 的复制品——对一个复制品写,
写了也回不去(回忆第 5 章:Go 默认传值)。改之前先问 CanSet():
val.CanSet() // → false图说:照镜子的默认规则是「只许看,不许碰」。想改?得先给反射一个 「能寻址的入口」(传指针再解引用),那是进阶用法,本书止于 CanSet11。
判断(我们的,不是书里的): 反射三定律的本质,是 Go 静态类型系统的 「海关」:任何运行时类型信息,都必须经空接口这一关进出。 这解释了反射的全部别扭之处——类型丢失、要断言回来、改值要寻址—— 也解释了它的代价从哪来。理解海关模型后,reflect 的 API 再繁杂也是 同一套手续。 如果错,会错在: Go 1.18 的泛型已经拿走了一大批「本来被迫用反射」的场景 (比如通用容器、通用排序);未来反射的地盘会收缩到「真·运行时才存在的信息」 (结构体标签、插件式调用)。这个趋势本书出版时已在发生。
4. 作者的判断与证据
| 书里的说法 | 性质 |
|---|---|
| 「反射用于通用编程/框架,避免用于性能关键代码」 | 作者主动划的边界,与 Go 官方文档口径一致 |
| 「反射有开销、削弱类型安全、难维护」 | 事实判断,属实 |
| 三定律的表述 | 与 Go 官方博客《The Laws of Reflection》一致 |
5. 边界与局限
- 没讲「怎么改值」:只停在 CanSet 的 false;真要改,得
reflect.ValueOf(&x).Elem()取可寻址的入口——本书没走这一步; - 没讲结构体标签(tag)的读取(
Field(i).Tag)——这是反射最常见的 实战用途(JSON/ORM 全靠它); - 没讲 Method/Call(运行时调方法)、MakeFunc 等高阶能力;
- 书中 TypeOf/ValueOf 的例子混用了「对学生值」和「对反射对象再反射」, 层次容易看晕,照主线例子的五步顺序走就不会乱。
6. 可带走的
- 反射 = 运行时照镜子:不问源码,问出值的类型、字段、值;
- 场景:编译时类型未知(空接口传进来)、写对任意类型都成立的通用代码;
- 边界:反射进框架和通用库,不进性能关键路径和类型安全要紧的地方;
reflect.TypeOf(x)问类型;.Name()短名,Kind()源头类别(struct/int/…);reflect.ValueOf(x)问值;.Field(i)按序取字段,NumField()数字段;.String()/.Int()/.Float()把反射值还原成具体类型(Int→int64,Float→float64);- Type 是你定义的类型,Kind 是它的源头类别——分情况处理看 Kind;
- 定律一:接口 → 反射对象;定律二:反射对象 → 接口(用 .Interface());
- 定律三:改反射对象,值必须可设置;默认 CanSet = false,硬改 panic;
- 浮点在反射里照样丢精度(float32 → float64 会露出尾巴)。
7. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 反射定义与动机 | Reflection | text/26-fm-learning-go-programming.txt:61(搜「exploration of any data」) · text/26-fm-learning-go-programming.txt:91(搜「type of slice at runtime」) |
| 使用边界 | Reflection | text/26-fm-learning-go-programming.txt:94(搜「avoided in performance-critical code」) |
| reflect 包总述 | Reflection | text/26-fm-learning-go-programming.txt:100(搜「dynamic type of a variable」) |
| TypeOf 与 Name | Reflection | text/26-fm-learning-go-programming.txt:184(搜「main.student」) · text/26-fm-learning-go-programming.txt:187(搜「reflect.rtype」) |
| ValueOf | Reflection | text/26-fm-learning-go-programming.txt:262(搜「{Akansha 17}」) |
| Kind 与 Type 的区别 | Reflection | text/26-fm-learning-go-programming.txt:268(搜「source type」) · text/26-fm-learning-go-programming.txt:331(搜「kind of 'stu_type' variable: struct」) |
| NumField | Reflection | text/26-fm-learning-go-programming.txt:397(搜「number of fields in 'stu': 2」) |
| Field 遍历 | Reflection | text/26-fm-learning-go-programming.txt:460(搜「value of 1th field」) |
| String/Int/Float 与精度 | Reflection | text/26-fm-learning-go-programming.txt:550(搜「int64 and value is 17」) · text/26-fm-learning-go-programming.txt:553(搜「40000.56640625」) |
| 三定律 | Reflection | text/26-fm-learning-go-programming.txt:562(搜「interface value to reflection object」) · text/26-fm-learning-go-programming.txt:640(搜「inverse of the first law」) · text/26-fm-learning-go-programming.txt:726(搜「must be settable」) |
| CanSet 与 panic | Reflection | text/26-fm-learning-go-programming.txt:765(搜「unaddressable value」) · text/26-fm-learning-go-programming.txt:807(搜「Hello false」) |
Footnotes
-
出处:「Reflection」第 61–64 段(
text/26-fm-learning-go-programming.txt:61,搜「exploration of any data」)。 ↩ -
出处:「Reflection」第 67–91 段(
text/26-fm-learning-go-programming.txt:91,搜「type of slice at runtime」)。 ↩ -
出处:「Reflection」第 94 段(
text/26-fm-learning-go-programming.txt:94,搜「avoided in performance-critical code」)。 ↩ -
出处:「Reflection」第 103–187 段(
text/26-fm-learning-go-programming.txt:184,搜「main.student」;text/26-fm-learning-go-programming.txt:187,搜「reflect.rtype」)。 ↩ -
出处:「Reflection」第 190–262 段(
text/26-fm-learning-go-programming.txt:262,搜「{Akansha 17}」)。 ↩ -
出处:「Reflection」第 265–334 段(
text/26-fm-learning-go-programming.txt:268,搜「source type」;text/26-fm-learning-go-programming.txt:331,搜「kind of 'stu_type' variable: struct」)。 ↩ -
出处:「Reflection」第 337–460 段(
text/26-fm-learning-go-programming.txt:397,搜「number of fields in 'stu'」;text/26-fm-learning-go-programming.txt:460,搜「value of 1th field」)。 ↩ -
出处:「Reflection」第 463–553 段(
text/26-fm-learning-go-programming.txt:547,搜「40000.566」;text/26-fm-learning-go-programming.txt:550,搜「int64 and value is 17」;text/26-fm-learning-go-programming.txt:553,搜「40000.56640625」)。 ↩ -
出处:「Reflection」第 556–637 段(第一定律见第 562 段,搜「interface value to reflection object」;示例输出见第 631–637 段)。 ↩ ↩2
-
出处:「Reflection」第 640–723 段(第二定律见第 640 段,搜「inverse of the first law」;
value.Interface()与还原见第 673–723 段)。 ↩ -
出处:「Reflection」第 726–807 段(第三定律见第 726 段,搜「must be settable」;panic 原文见第 765 段,搜「unaddressable value」;CanSet 示例见第 807 段,搜「Hello false」)。 ↩