之前在博客中有讲Reflect的使用, 然而使用后仍然对于Reflect存在疑惑, 知其然不知其所以然. 所以本文讲如何理解Reflect 在Go中的工作原理;
前提
这一部分讲述, 理解Reflect所需要的前置知识;
众所周知Reflect提供了程序运行时检查数据结构, 修改数据内容的能力.
在Go中, 其依赖两个Feature in Go: Type, Interface.
Type
Go是**静态类型(static type)**语言, 其类型在编译时就已经固定了. 例如:
1 | package main |
输出为:
1 | Variable i type info is: int |
可以发现, 即使i, j的底层类型(underlying type)一致, 对于Go来说其static type是不一致的;
Interface
Interface Type
在Type中存在一个特殊的类型Interface, 其表示某特定方法集.只要某个具体变量concrete variable满足Interface声明的方法集, 那么就可以通过Interface variable接收此concrete variable. 例如io.Reader, io.Writer:
1 | type Reader interface { |
只要实现了Read/Write方法, 就可以使用interface variable接收:
1 | var r io.Reader |
上面的例子中, 将*os.File类型,其实现了Read,的变量tty赋值给了interface variable: r;
Empty Interface Type
在Interface中也有一个特殊的类型: 空接口Empty Interface: interface {}, 别名为any. 我们都知道, Empty interface中不包含任何方法, 任意的类型, 都实现了Empty Interface, 也就是说:
Empty Interface Variable可以接收任意Variable;
1 | var i any |
上面通过Empty Interface Variable: i来接收tty当然是没问题的;
Interface Variable Representation
而对于Interface Variable其表现形式为: 值与类型对 (Value, Concrete Type) pair: Interface Variable存储了一个具体的值, 与这个具体的值的类型;
- 具体的值: 指接口内接收的值;
- 具体的类型: 指的是这个值所代表的类型, 由于
Go是静态类型语言, 这个类型中不仅仅包含有满足当前Interface Type的内容, 也包含有其他类型自定义的类型; 简单说: 这个类型, 就是值的类型, 其具备这个类型中定义的所有内容.下面的例子将解释:
1 | package main |
输出为:
1 | Variable r type info is: *os.File |
我们发现, 可以通过断言(assert)的方式, 将Reader Interface Variable赋值给Writer Interface Variable. 这是因为Reader Interface Variable: r的Concrete type: *os.File也实现了Write方法. 也就是说通过Interface Variable接收Concrete Variable并不会丢失任何concrete type的信息, 其仅仅会约束Interface Variable的调用: 例如r 仅可以调用Read方法.
对于empty interface来说, 则可以直接赋值, 不需要assert: 因为empty interface是所有类型的子集:
1 | ... |
三个反射定律
From Interface value To Reflect Object
Go中的reflect pkg核心Object为: reflect.Type, reflect.Value; 这两种类型, 赋予了编程人员获取Interface Value中(Value, Concrete Type) pair的能力.
获取:
reflect.Type的方式:reflect.TypeOf1
2
3
4
5
6
7
8
9
10
11package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}输出为:
type: float64;上面的例子中, 通过
reflect.TypeOf方法获取了x的类型信息, 并输出.
然而我们要阐述的是Interace value -> reflect Object的转换, 不是concrete value -> reflect Object
对于Go有所了解的朋友肯定意识到,reflect.TypeOf(x)一定是做了隐式转换, 将concrete value先转换为了interface value, 随后在对interface value进行了提取其type信息的操作;
这可以通过reflect.TypeOf的函数签名印证此推论:1
2
3
4
5// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i any) Type {
...
}可以看到
reflect.TypeOf的入参类型为empty interface: any. 对于Go的函数调用来说, 其均为值传递, 会复制一份数据, 并赋值给函数入参, 所以上面的示例可以通过伪代码描述为:1
2var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(i = copy(x)))获取:
reflect.Type的方式:reflect.ValueOf1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("reflect value:", reflect.ValueOf(x))
fmt.Println("value string:", reflect.ValueOf(x).String())
var y string = "SUN"
fmt.Println("reflect value:", reflect.ValueOf(y))
fmt.Println("value string:", reflect.ValueOf(y).String())
}输出为:
1
2
3
4reflect value: 3.4
value string: <float64 Value>
reflect value: SUN
value string: SUN上面的例子中通过
reflect.ValueOf方法, 将interface value转换为reflect.Value;reflect.ValueOf的函数签名入参也是empty interface类型, 其参数调用的过程与reflect.TypeOf是一致的, 这里不赘述;需要注意的是:
reflect.ValueOf获得的是reflect.Value对象,reflect.VauleOf.String获得的是string对象. 如果对于interface value其可以转换为string的话, 则输出value本身, 例如"SUN"; 如果不可以转换的话, 则输出格式是固定的<T Value>, 例如3.4;
reflect也提供了reflect.Value, reflect.Type的操作方法:
reflect.Value.Type: 将reflect.Value -> reflect.Type;reflect.Value.Kind,reflect.Type.Kind: 获取reflct.Object的underlying type;- …
由于并非本文重点, 这里不一一解释;
From Reflect ObjectTo Interface Value
既然上面可以将Interface value转换为reflect Object, 那么这个过程也可以反转, 其方法为: reflect.Object.Interface; 这里以reflect.Value为例:
1 | package main |
输出为: Interface value: 3.4.
在上面的例子中通过Interface()方法, 将reflect.Value再次封装为(Value, Concrete Type) pair形式的Interface Value, 其类型与值信息并未改变;
如果underlying type类型一致的情况下, 是否会发生类型变动呢? 答案是否定的:
1 | package main |
输出为:
1 | Interface value: 3.4 |
在上面的例子中, 我们定义了一个自定义类型 MyFloat,并创建了一个变量 x,类型为 MyFloat,值为 3.4。我们通过reflect.Interface方法, 获得了一个Interface Value: i, 并再次检查其类型, 发现其与最初的变量x的static type一致: MyFloat, 当然啦Underlying type是float64; 上面的例子说明了Interface value -> reflcet Object -> Interface value的过程是幂等的, 对于Interface Value的值与类型两种信息, 均没有丢失;
Modify a Reflect Object
利用reflect可以修改reflect Object的前提是settable;何为settable?
错误示例:
1
2
3
4
5
6
7
8
9
10
11package main
import (
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.
}输出为:
1
2
3
4
5panic: reflect: reflect.Value.SetFloat using unaddressable value
goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x7f2889534108?)
....上面是示例中, 运行报错; 其提示为: 使用了无法引用的值; 其实这里的报错是不精准的, 其核心是
v是unsettable状态, 而不是unaddressable;我们可以通过
CanSet方法, 判定是否可修改:1
2
3
4
5
6
7
8
9
10
11
12package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("settability of v:", v.CanSet())
}输出为:
settability of v: false正确示例:
我们需要使用
reflect.ValueOf(&x).Elem()来获取x的可设置反射值,然后才能使用v.SetFloat(7.1)来修改变量x的值。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
v := reflect.ValueOf(&x).Elem()
fmt.Println("settability of v:", v.CanSet())
v.SetFloat(7.1)
fmt.Println("修改后的 x 的值:", x)
}输出为:
settability of v: true <br> 修改后的 x 的值: 7.1;
何为settable? 其与addressable很像但不一样, 其表示是否可以通过v找到原始变量的值, 或者说是 reflect.Object是否存储了原始的值;
为何对于reflect.ValueOf(x)就不可以, 对于reflect.ValueOf(&x).Elem就可以呢? 这是由于Go中函数传参是值传递的特性导致的;reflect.ValueOf(x) == reflect.Vaule(i = copy(x)).那么传值本身是绝对无法获取到原始值的, 传指针则有可能获得指针指向的值本身.为了更清晰的表示, 这里再写一个例子:
1 | package main |
输出为:
1 | type of v: *float64 |
上面的例子中将reflect.ValueOf.Elem过程拆分为两步: reflect.ValueOf, reflect.ValueOf.Elem, 并分别检验其settable. 发现只要是直接传参的都不可以更改, 只有这种reflect.ValueOf(&x).Elem形式才是找到了原始值存储的地址;
对于struct的修改也是异曲同工, 网上例子很多, 这里不赘述了;
结论
本文介绍了reflect的三定律:
- From
Interface valueTo `Reflect Object - From
Reflect ObjectToInterface Value - Modify a
Reflect Object
一旦理解了上述过程, 对于实际使用时可以减少不必要的困惑. 当然了, 还是那个建议, 能不用反射就别用;