什么是反射
反射是指程序在运行期间,动态地更新、获取变量的值,包括获取字段类型、名称、调用类变量对应的方法等。
使用反射,不需要在编译时就确定变量的类型,而可以在运行时去动态地获取,这更灵活。
reflect 包的使用
Value 与 Type
对于对象,是由 类型 和 值 两部分组成的。
相应地 reflect 包也分为两大部分 value
与 type
,并提供了两种初始化函数 TypeOf()
、ValueOf()
,用于获取对应的 类型 和 值。
而对于值,它一定是依附于类型而存在的,故值一定有对应的类型。因此可通过 ValueOf().Type
获取对应的类型,其效果等同于 TypeOf()
读取
通过 Value.Kind()
来获取变量的类型,当然 Type.Kind()
也可以。
Array、Slice
使用 reflect.Value.Len()
获取数组长度,然后使用 reflect.Value.Index()
获取数组中的元素
1
2
3
4
|
v := reflect.ValueOf(i)
for i:=0; i<v.Len(); i++ {
fmt.Println(v.Index(i))
}
|
还可使用 reflect.Value.Cap()
获取其容量大小
Struct
field
使用 reflect.Value.NumField()
获取结构体中成员个数。用 reflect.Value.Field()
获取结构体中的成员。用 reflect.Value.Field().Tag
获取结构体中的成员的 tag
1
2
3
4
|
v := reflect.ValueOf(i)
for i:=0; i<v.NumField(); i++ {
fmt.Println(v.Index(i))
}
|
method
对于结构体中的方法,可用 NumMethod
、Method
方法来操作。
而方法的参数、返回值,则用 NumOut
、Out
来操作
当然也可使用 Call
来调用该方法
1
2
3
4
5
|
v := reflect.ValueOf(i)
for i:=0; i<v.NumMethod(); i++ {
fmt.Println(v.Index(i))
fmt.Printf("has %d out and %d in", v.Index(i).NumOut(), v.Index(i).NumIn())
}
|
虽然 reflect.TypeOf().Method
与 reflect.ValueOf().Method
均能表示对应的方法,但两者略有不同。
Type.Method
表示的是一个描述了返回值、参数、方法名称等信息的结构体,不绑定到任何对象上;而 Value.Method
在前者的基础上,还绑定了相应的额对象(receiver),可以调用 Call
方法。
修改
在修改对象前,我们得先判断是否能修改,涉及以下两个方法:
CanAddr
是否可取地址。
Go 中部分类型的变量是不可寻址的:
- Map:map 内部会变动其元素
- String:string 是不可变值,修改了就会重新创建整个 string
- 常量
- 中间值
- 等等
CanSet
判断某对象是否可修改。
只有当对象可取地址 且 可导出(为 struct 的 filed 时) 时,才能被修改
确认对象可修改后,可使用reflect.ValudOf(&x).Elem().SetInt
或者 reflect.ValudOf(&x).Elem().Set(reflect.ValueOf(66))
来修改对象的值
当然,除了 SetInt
还有 SetString
、SetBytes
等方法,也可用 SetCap
来修改数组对象的容量
Go 反射原理
为什么 reflect
能获取到变量的类型,就得先了解 Go 中变量的数据格式。
在 Go 中,变量的内部结构分为两大部分:类型 和 值。相应地,反射对象的内部结构分为 类型 与 值。
Go 反射通过将 变量 强制类型转化 为反射对象,然后读取反射对象中的 Value
与 Type
的值,就拿到变量相关的信息。
应用:json 编码
Go 中的 json 包,能将任意数据类型编码为 json 字符串。
我们可通过实现 json 编码器,来练习 reflect
包的使用,具体实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
func Marshal(v interface{}) (string, error) {
return marshal(reflect.ValueOf(v))
}
func marshal(v reflect.Value) (string, error) {
switch v.Kind() {
case reflect.String:
return "\"" + v.String() + "\"", nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return fmt.Sprintf("%d", v.Int()), nil
case reflect.Float32, reflect.Float64:
return fmt.Sprintf("%f", v.Float()), nil
case reflect.Array, reflect.Slice:
var retArray []string
for i := 0; i < v.Len(); i++ {
retString, err := marshal(v.Index(i))
if err != nil {
return "", err
}
retArray = append(retArray, retString)
}
return "[" + strings.Join(retArray, ",") + "]", nil
case reflect.Bool:
if v.Bool() {
return "true", nil
} else {
return "false", nil
}
case reflect.Map:
var retArray []string
for v.MapRange().Next() {
key, err1 := marshal(v.MapRange().Key())
if err1 != nil {
return "", err1
}
value, err2 := marshal(v.MapRange().Value())
if err2 != nil {
return "", err1
}
retArray = append(retArray, "\""+key+"\":"+value)
}
return fmt.Sprintf("{%s}", strings.Join(retArray, ",")), nil
case reflect.Struct:
var retArray []string
for i := 0; i < v.NumField(); i++ {
value, err := marshal(v.Field(i))
if err != nil {
return "", err
}
retArray = append(retArray, fmt.Sprintf("\"%s\":%s", v.Type().Field(i).Name, value))
}
return fmt.Sprintf("{%s}", strings.Join(retArray, ",")), nil
default:
return "", errors.New("unsupport type")
}
}
|
调用 Marshal
方法,将变量编码为 json 字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
type Person struct {
age int64
Name string
Hobby []string
}
func main() {
p := Person{
age: 20,
Name: "www",
Hobby: []string{
"a", "b",
},
}
s, err := Marshal(p)
fmt.Println(s, err)
}
|
输出为
1
|
{"age":20,"Name":"www","Hobby":["a","b"]}
|
反射的缺点
虽然反射使用方便,功能强大,但是滥用它也会造成一些问题
使得程序可读性更差
由于反射是在程序运行时去获取变量类型并解析之,所以在开发阶段 IDE 无法知道其类型,无法给出有效的代码提示;在编译阶段无法获知其类型,编译时的类型错误检查机制也就失效了。
大量使用反射导致程序性能低
编写两个函数,用反射、直接读取 两种方式读取变量的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
import (
"reflect"
"testing"
)
func BenchmarkNormal(t *testing.B) {
for i := 0; i < t.N; i++ {
Normal(1)
}
}
func BenchmarkRef(t *testing.B) {
for i := 0; i < t.N; i++ {
Ref(1)
}
}
func Normal(n int64) {
_ = n
}
func Ref(n int64) {
_ = reflect.ValueOf(n).Int()
}
|
压测结果如下
1
2
|
BenchmarkNormal-16 1000000000 0.2375 ns/op
BenchmarkRef-16 242498898 4.589 ns/op
|
结果来看,使用反射,运行速度慢约 1 个数量级。
那么究竟慢在哪里?使用 profiler 来看看
从图中看到,使用反射获取变量的值,要经过很多步骤,比如拷贝值、拼装相应的反射对象、类型转换等。经过这些操作就不如直接读取变量值来得快。
参考资料
http://longlog.me/2019/10/23/go-addressable/
图解go反射实现原理
http://legendtkl.com/2016/08/06/reflect-inside/