Go 反射的三大法则
本文翻译自:https://go.dev/blog/laws-of-reflection,有删改。
核心要点
Go 语言的三大法则提供了对变量的便捷操作,使得可以实现更多的功能。核心要点在于 go 的变量和反射对象之间的转换,相当于两个不同的空间,可以类比于通信领域的时域和频域的区别,是一个东西的两种不同观察角度,通过反射可以获取变量在”反射域“中的信息,从而赋予程序更多的功能。
这篇文章主要是介绍了 Go 语言中反射的三大法则,官方的这篇文章中也给了一些示例代码,但是缺乏了在实际项目中对反射的应用,后续将补充下反射在实际项目中的应用。
Go types 和 interfaces
计算中的反射是程序检查自身结构的能力,特别是通过类型;它是元编程的一种形式。
Go 语言是强类型的,每个变量都有一个静态类型,也就是说在编译时就会确定一个已知并固定的类型。看下如下代码:
那么 i 是 int 类型,j 是 MyInt 类型,这两个变量是不同的静态类型,尽管它们有相同的基础类型。它们不能不经过转换直接赋值给对方。
一个重要的类型是 interface 类型,代表了固定的方法集。
一个 interface 变量可以存储任意具体(non-interface)的值,只要这个值实现了 interface 的方法。一个比较明显的例子就是:io.Reader 和 io.Writer
任何一个类型只要实现了 Read 或者是 Write 方法带有这个签名的都可以说是实现了 io.Reader 或者 io.Writer。这意味着 io.Reader 类型,可以保存任意一个实现了 Read 方法的类型。
有一点很重要,不管 r 存储了任何具体的值,r 的类型都是 io.Reader。
一个非常重要的 interface 类型的例子是空 interface,也就是 interface{}
, 等同于别名:any
, 它表示空的方法集,任何值都可以满足它,因为每个值都有零个或多个方法。
有些人说 Go 语言的 interface 是动态类型,这种说法是误导。它们都是静态类型,一个 interface 类型的变量一直都是一样的类型,尽管 interface 变量在运行时存储的内容变化可能导致类型变化,但是这个值一直都是满足这个 interface 的。
三大法则
1. 反射从 interface 值到 反射对象
基本功能上,反射仅仅是一种检查类型的机制和存储在 interface 变量中的值对。开始前需要知道两个类型 reflect 包中的 Type 和 Value。这两种类型允许访问 interface 变量的内容,还有两个简单的函数 reflect.TypeOf 和 reflect.ValueOf,从 interface 值中获取 reflecet.Type 和 reflect.Value。
输出结果:
在这里你可能会奇怪这里没有 interface,而是将 float64 类型的变量 x 传给 reflect.TypeOf。但是 reflect.TypeOf 包含一个空的接口。当我们调用 reflect.TypeOf ,x 首先存储在一个 empty interface 中,然后作为参数传入。reflect.TypeOf 解出 empty interface 来恢复类型信息。
reflect.ValueOf 相应的恢复出 Value 了。
输出结果:
这里显式调用了 String 方法是因为默认 fmt 包会深入 reflect.Value 来显示具体的值,但是 String 方法不会。???
reflect.Type 和 reflect.Value 有很多的方法来检查和操作。一个重要的例子是 Value 有一个 Type 方法来返回 reflect.Value 的 Type。另外一个是 Type 和 Value 都有一个 Kind 方法返回一个常数表示它们存储的是什么项:Uint, Float64,Slice 等。Value 有 Int 和 Float 方法让我们来获取具体的值(对应的类型是 int64 和 float64)。
输出结果:
另外也有 SetInt 和 SetFloat 方法,但是使用它们之前我们需要理解 settability,这个是反射的第三法则
反射库有几个值得注意的属性。首先,为了保持 API 简单,Value 的 "getter" 和 “setter” 方法是在大的类型上存储值的,比如说 int64 用于存储所有有符号的整数。所以,Value 的 Int 方法返回类型是 int64,SetInt 设置值输入参数为 int64,当使用到得时候需要转化成实际的类型:
第二个特性是说一个反射对象的 Kind 方法返回的基本类型,而不是静态类型,如果一个反射对象包含了用户定义的整数类型,那么 Kind 返回的还是 reflect.Int,尽管 x 的静态类型是 MyInt,而不是 int。也就是说,Kind 无法区分 int 和 MyInt,Type 可以。
2. 从反射对象到 interface 值
给定一个 reflect.Value,可以使用 Interface 方法恢复 interface value,实际上这个方法将 type 和 value 信息重新打包到 interface 的表示中返回该结果:
对于 fmt.Println 和 fmt.Printf 等都传入的是 empty interface values,在 fmt 内部将其解析出来。因此正确打印 reflect.Value 的内容做法是将 Interface 方法返回值传给 print 函数
fmt 库后买你有做了更新,它会自动解析 reflect.Value,所以可以这样写,会得到同样的结果:
但是为了代码更加清晰,应保留 Interface() 的调用。总结来说,Interface 方法是 ValueOf 函数的逆向,除了它返回的结果类型是 interface{}
3. 要修改反射对象,它的值必须是可设置的
第三定律是最微妙和令人困惑的,但如果我们从基本原理开始,它很容易理解。
输出结果:
问题并不在于说 7.1 这个值是不能寻址的,而是 v 是不可设置的。可设置是反射 Value 的一个属性,并不是所有的 Value 都有这个属性。
Value 的 CanSet 能返回 Value 是否可以设置:
输出结果:
在一个不允许设置的 Value 中调用 Set 方法会导致出错,那么什么是可设置的呢?可设置性有点像可寻址性,但更为严格。通过这个属性,反射对象可以修改用于创建反射对象的实际存储。可设置性取决于反射对象是否保存原始项。
在这里将 x 传给 ValueOf,其实是传了一份 x 的拷贝,而不是 x 本身。所以当执行 v.SetFloat(7.1)
的时候如果成功,并不会更新 x 的值,尽管看起来 v 是从 x 创建的,但是它更新的是 x 的拷贝中存储的反射值,x 本身不会受到影响。这将是令人困惑和无用的,所以它是非法的,可设置性是用来避免这个问题的属性。
如果这看起来很奇怪,其实并不奇怪。这其实是一个熟悉的情况下,不寻常的装束。考虑将 x 传递给函数:f(x)
,如果我们希望函数 f 能够修改 x 的值,但是我们传的是 x 的拷贝,所以如果希望直接修改 x,那么应该把 x 的指针传给 f,也就是f(&x)
。
这是简单和熟悉的,反射的工作方式也是一样的。如果要通过反射修改 x,必须给反射库一个指针,指向要修改的值。
下面来看一个例子:
输出结果:
反射对象 p 是不可设置的,但它不是我们想设置的 p,它是(实际上)*p。为了得到 p 所指向的对象,我们调用 Value 的 Elem 方法,它直接指向指针,并将结果保存在一个名为 v 的反射值中:
现在的 v 就是一个可以设置的反射对象了,输出结果为:
这个时候设置来修改 v 的值,那么 x 的值也会发生变化:
输出结果:
反射可能很难理解,但它的功能与语言完全一样,尽管它通过反射类型和值来掩盖所发生的事情。只要记住,反射值需要某个东西的地址,以便修改它们所表示的内容。
结构体 struct
下面一个例子用于分析结构体的 value,创建结构体对象的取址反射对象,因为后面我们想修改它,然后,我们将 typeOfT 设置为它的类型,并使用简单的方法调用遍历字段(有关详细信息,请参阅包反射)。注意,我们从结构类型中提取字段的名称,但字段本身是常规反射。值对象。
输出结果:
在这里还有一点关于可设置性,结构体的 T 的 field name 是大写的,可导出的,只有可导出的 field 才可以设置因为 s 包含一个可设置的反射对象,所以我们可以修改结构的字段。
输出结果:
如果我们修改程序,使 s 是从 t 而不是 &t 创建的,那么对 SetInt 和 SetString 的调用就会失败,因为 t 的字段是不可设置的。
总结
三个法则:
Reflection goes from interface value to reflection object.
Reflection goes from reflection object to interface value.
To modify a reflection object, the value must be settable.
参考
The laws of Refelection 这个文章很有趣,有这样一段话,这篇文章写于 2011 年,但是居然在 2022 年,go 1.18 提出泛型的时候加入了这个注释,很棒。
版权声明: 本文为 InfoQ 作者【linlh】的原创文章。
原文链接:【http://xie.infoq.cn/article/044d5861d61bc85040295e6a7】。文章转载请联系作者。
评论