跳转至

反射:程序在运行时照镜子,以及它的三条铁律

这一章讲 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 包内部的 rtype

TypeOf 吃空接口、吐类型的描述对象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.566

Field(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. 可带走的

  1. 反射 = 运行时照镜子:不问源码,问出值的类型、字段、值;
  2. 场景:编译时类型未知(空接口传进来)、写对任意类型都成立的通用代码;
  3. 边界:反射进框架和通用库,不进性能关键路径和类型安全要紧的地方;
  4. reflect.TypeOf(x) 问类型;.Name() 短名,Kind() 源头类别(struct/int/…);
  5. reflect.ValueOf(x) 问值;.Field(i) 按序取字段,NumField() 数字段;
  6. .String()/.Int()/.Float() 把反射值还原成具体类型(Int→int64,Float→float64);
  7. Type 是你定义的类型,Kind 是它的源头类别——分情况处理看 Kind;
  8. 定律一:接口 → 反射对象;定律二:反射对象 → 接口(用 .Interface());
  9. 定律三:改反射对象,值必须可设置;默认 CanSet = false,硬改 panic;
  10. 浮点在反射里照样丢精度(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」)

  1. 出处:「Reflection」第 61–64 段(text/26-fm-learning-go-programming.txt:61,搜「exploration of any data」)。 ↩

  2. 出处:「Reflection」第 67–91 段(text/26-fm-learning-go-programming.txt:91,搜「type of slice at runtime」)。 ↩

  3. 出处:「Reflection」第 94 段(text/26-fm-learning-go-programming.txt:94,搜「avoided in performance-critical code」)。 ↩

  4. 出处:「Reflection」第 103–187 段(text/26-fm-learning-go-programming.txt:184,搜「main.student」;text/26-fm-learning-go-programming.txt:187,搜「reflect.rtype」)。 ↩

  5. 出处:「Reflection」第 190–262 段(text/26-fm-learning-go-programming.txt:262,搜「{Akansha 17}」)。 ↩

  6. 出处:「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」)。 ↩

  7. 出处:「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」)。 ↩

  8. 出处:「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」)。 ↩

  9. 出处:「Reflection」第 556–637 段(第一定律见第 562 段,搜「interface value to reflection object」;示例输出见第 631–637 段)。 ↩↩

  10. 出处:「Reflection」第 640–723 段(第二定律见第 640 段,搜「inverse of the first law」;value.Interface() 与还原见第 673–723 段)。 ↩

  11. 出处:「Reflection」第 726–807 段(第三定律见第 726 段,搜「must be settable」;panic 原文见第 765 段,搜「unaddressable value」;CanSet 示例见第 807 段,搜「Hello false」)。 ↩