interface

反射是基于interface底层的。先复习一下空interface的底层:

1
2
3
4
type eface struct {
_type *_type
data unsafe.Pointer
}

第一个字段是接口存放实体的类型详细信息,第二个字段放的是实体的地址

反射基本函数源码

反射包里面定义个一个接口和一个结构体,即 reflect.Typereflect.Value,它们提供很多函数来获取存储在接口里的类型信息。

reflect.Type主要提供了关于类型相关的信息,所以它和_type关联比较紧密,后者则结合_typedata两者,因此程序员可以获取甚至改变类型的值

可以使用两个基础的关于反射的函数来获取上述的接口和结构体:

1
2
func TypeOf(i interface{}) Type 
func ValueOf(i interface{}) Value

关于源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}

func toType(t *rtype) Type {
if t == nil {
return nil
}
return t
}

type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}

这里的 emptyInterface 和上面提到的 eface 是一回事(字段名略有差异,字段是相同的),且在不同的源码包:前者在 reflect 包,后者在 runtime 包。 eface.typ 就是动态类型。

可以看到,TypeOf的作用,简而言之就是从传入的接口变量i,转化为eface,然后从eface中取出保存实体类型信息的_type作为入参,传入toType函数之中。toTypeeface.typ为实体,返回一个Type接口,这就是我们所需要的。

而我们获取到的Type接口则根据实体的_type保存了与实体有关的各种信息

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
type Type interface {
// 所有的类型都可以调用下面这些函数

// 此类型的变量对齐后所占用的字节数
Align() int

// 如果是 struct 的字段,对齐后占用的字节数
FieldAlign() int

// 返回类型方法集里的第 `i` (传入的参数)个方法
Method(int) Method

// 通过名称获取方法
MethodByName(string) (Method, bool)

// 获取类型方法集里导出的方法个数
NumMethod() int

// 类型名称
Name() string

// 返回类型所在的路径,如:encoding/base64
PkgPath() string

// 返回类型的大小,和 unsafe.Sizeof 功能类似
Size() uintptr

// 返回类型的字符串表示形式
String() string

// 返回类型的类型值
Kind() Kind

// 类型是否实现了接口 u
Implements(u Type) bool

// 是否可以赋值给 u
AssignableTo(u Type) bool

// 是否可以类型转换成 u
ConvertibleTo(u Type) bool

// 类型是否可以比较
Comparable() bool

// 下面这些函数只有特定类型可以调用
// 如:Key, Elem 两个方法就只能是 Map 类型才能调用

// 类型所占据的位数
Bits() int

// 返回通道的方向,只能是 chan 类型调用
ChanDir() ChanDir

// 返回类型是否是可变参数,只能是 func 类型调用
// 比如 t 是类型 func(x int, y ... float64)
// 那么 t.IsVariadic() == true
IsVariadic() bool

// 返回内部子元素类型,只能由类型 Array, Chan, Map, Ptr, or Slice 调用
Elem() Type

// 返回结构体类型的第 i 个字段,只能是结构体类型调用
// 如果 i 超过了总字段数,就会 panic
Field(i int) StructField

// 返回嵌套的结构体的字段
FieldByIndex(index []int) StructField

// 通过字段名称获取字段
FieldByName(name string) (StructField, bool)

// FieldByNameFunc returns the struct field with a name
// 返回名称符合 func 函数的字段
FieldByNameFunc(match func(string) bool) (StructField, bool)

// 获取函数类型的第 i 个参数的类型
In(i int) Type

// 返回 map 的 key 类型,只能由类型 map 调用
Key() Type

// 返回 Array 的长度,只能由类型 Array 调用
Len() int

// 返回类型字段的数量,只能由类型 Struct 调用
NumField() int

// 返回函数类型的输入参数个数
NumIn() int

// 返回函数类型的返回值个数
NumOut() int

// 返回函数类型的第 i 个值的类型
Out(i int) Type

// 返回类型结构体的相同部分
common() *rtype

// 返回类型结构体的不同部分
uncommon() *uncommonType
}

再看一下ValueOf函数: 我们经常要在需要改变实体的情况下用到它,所以需要结合类型信息的值信息,所以Value既要包含_type还要包含data

国际惯例先上源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}

// ……
return unpackEface(i)
}

// 分解 eface
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))

t := e.typ
if t == nil {
return Value{}
}

f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}

主要来看这个分解eface,其实就是把i转化为eface,然后取出它的_type和data,组装起来称为一个结构体Value。

Value 结构体定义了很多方法,通过这些方法可以直接操作 Value 字段 ptr 所指向的实际数据:

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
// 设置切片的 len 字段,如果类型不是切片,就会panic
func (v Value) SetLen(n int)

// 设置切片的 cap 字段
func (v Value) SetCap(n int)

// 设置字典的 kv
func (v Value) SetMapIndex(key, val Value)

// 返回切片、字符串、数组的索引 i 处的值
func (v Value) Index(i int) Value

// 根据名称获取结构体的内部字段值
func (v Value) FieldByName(name string) Value

// 用来获取 int 类型的值
func (v Value) Int() int64

// 用来获取结构体字段(成员)数量
func (v Value) NumField() int

// 尝试向通道发送数据(不会阻塞)
func (v Value) TrySend(x reflect.Value) bool

// 通过参数列表 in 调用 v 值所代表的函数(或方法
func (v Value) Call(in []Value) (r []Value)

// 调用变参长度可变的函数
func (v Value) CallSlice(in []Value) []Value

需要注意的一点是,可以通过函数让interfaceTypeValue 三者转换,因为Value基本上存储了eface的所有信息。

反射三大定律

  1. Reflection goes from interface value to reflection object.
  1. Reflection goes from reflection object to interface value.
  1. To modify a reflection object, the value must be settable.

前两条是说接口型变量interface和反射类型变量reflect.Typereflect.Value可以相互转换。

第三条是说如果操作一个反射变量,那么它就必须是可以设置的。反射变量可设置的本质是它存储了原变量本身,这样对反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑。所以第二种情况在语言层面是不被允许的。

举一个例子:

1
2
3
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1) // Error: will panic.

这里在调用v := reflect.ValueOf(x)是使用的传值调用,也就是说对于float类型的变量,传进去的是它的一个拷贝,所以不可以。解决方法是传进去一个指针。

一个栗子

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package main

import (
"reflect"
"fmt"
)

type Child struct {
Name string
Grade int
Handsome bool
}

type Adult struct {
ID string `qson:"Name"`
Occupation string
Handsome bool
}

// 如果输入参数 i 是 Slice,元素是结构体,有一个字段名为 `Handsome`,
// 并且有一个字段的 tag 或者字段名是 `Name` ,
// 如果该 `Name` 字段的值是 `qcrao`,
// 就把结构体中名为 `Handsome` 的字段值设置为 true。
func handsome(i interface{}) {
// 获取 i 的反射变量 Value
v := reflect.ValueOf(i)

// 确定 v 是一个 Slice
if v.Kind() != reflect.Slice {
return
}

// 确定 v 是的元素为结构体
if e := v.Type().Elem(); e.Kind() != reflect.Struct {
return
}

// 确定结构体的字段名含有 "ID" 或者 json tag 标签为 `name`
// 确定结构体的字段名 "Handsome"
st := v.Type().Elem()

// 寻找字段名为 Name 或者 tag 的值为 Name 的字段
foundName := false
for i := 0; i < st.NumField(); i++ {
f := st.Field(i)
tag := f.Tag.Get("qson")

if (tag == "Name" || f.Name == "Name") && f.Type.Kind() == reflect.String {
foundName = true
break
}
}

if !foundName {
return
}

if niceField, foundHandsome := st.FieldByName("Handsome"); foundHandsome == false || niceField.Type.Kind() != reflect.Bool {
return
}

// 设置名字为 "qcrao" 的对象的 "Handsome" 字段为 true
for i := 0; i < v.Len(); i++ {
e := v.Index(i)
handsome := e.FieldByName("Handsome")

// 寻找字段名为 Name 或者 tag 的值为 Name 的字段
var name reflect.Value
for j := 0; j < st.NumField(); j++ {
f := st.Field(j)
tag := f.Tag.Get("qson")

if tag == "Name" || f.Name == "Name" {
name = v.Index(i).Field(j)
}
}

if name.String() == "qcrao" {
handsome.SetBool(true)
}
}
}

func main() {
children := []Child{
{Name: "Ava", Grade: 3, Handsome: true},
{Name: "qcrao", Grade: 6, Handsome: false},
}

adults := []Adult{
{ID: "Steve", Occupation: "Clerk", Handsome: true},
{ID: "qcrao", Occupation: "Go Programmer", Handsome: false},
}

fmt.Printf("adults before handsome: %v\n", adults)
handsome(adults)
fmt.Printf("adults after handsome: %v\n", adults)

fmt.Println("-------------")

fmt.Printf("children before handsome: %v\n", children)
handsome(children)
fmt.Printf("children after handsome: %v\n", children)
}

在弄懂了底层后,可以很清晰的看出这个例子中反射运行的机制。