Go 反射

Go 反射

Go语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。

反射是指在程序运行期对程序本身进行访问和修改的能力,程序在编译时变量被转换为内存地址,变量名不会被编译器写入到可执行部分,在运行程序时程序无法获取自身的信息。

支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

C/C++语言没有支持反射功能,只能通过 typeid 提供非常弱化的程序运行时类型信息;Java、C# 等语言都支持完整的反射功能;Lua、JavaScript 类动态语言,由于其本身的语法特性就可以让代码在运行期访问程序自身的值和类型信息,因此不需要反射系统。

Go语言程序的反射系统无法获取到一个可执行文件空间中或者是一个包中的所有类型信息,需要配合使用标准库中对应的词法、语法解析器和抽象语法树(AST)对源码进行扫描后获得这些信息。

Go语言提供了 reflect 包来访问程序的反射信息。

Type和Value方法

Type和Value拥有的同名方法

MethodType返回类型Value返回类型备注
KindKindKind返回指定对象的Kind类型
NumMethodintint返回struct拥有的方法总数,包括unexported方法
MethodByNameMethodValue根据方法名找方法
MethodMethodValue返回第i个方法
NumFieldintint返回struct所包含的field数量
FieldStructFieldValue取struct结构的第n个field
FieldByIndexStructFieldValue嵌套的方式取struct的field,比如v.FieldByIndex([]int{1,2})等价于 v.field(1).field(2)
FieldByNameStructFiel,boolValue返回名称匹配match函数的field
FieldByNameFuncStructField,boolValue返回名称匹配match函数的field

Type独有的方法

Method备注
Align分配内存时的内存对齐字节数
FieldAlign作为struct的field时内存对齐字节数
Nametype名 string类型
PkgPath包路径, “encoding/base64”, 内置类型返回empty string
Size该类型变量占用字节数
Stringtype的string表示方式
Implements判断该类型是否实现了某个接口
AssignableTo判断该类型能否赋值给某个类型
ConvertibleTo判断该类型能否转换为另外一种类型
Comparable判断该类型变量是否可以比较
ChanDir返回channel的方向 recv/send/double
IsVariadic判断函数是否接受可变参数
Elem取该类型的元素
In函数第n个入参
Out函数第n个出参
NumIn函数的入参数个数
NumOut函数的出参个数
Key返回map结构的key类型Type
Len返回array的长度

Value独有的方法

Method备注
Addrv的指针,前提时CanAddr()返回true
Bool取值,布尔类型
Bytes取值,字节流
Call调用函数
CallSlice调用具有可变参的函数
CanAddr判断能否取址
CanInterface判断Interface方法能否使用
CanSet判断v的值能否改变
Cap判断容量 Array/Chan/Slice
Close关闭Chan
Complex取值,复数
Convert返回将v转换位type t的结果
Elem返回interface包含或者Ptr指针的实际值
Float取值,浮点型
Index 索引操作Array/Slice/String
Int取值,整型
Interface将当前value以interface{}形式返回
IsNil判断是否为nil,chan, func, interface, map, pointer, or slice value
IsValid是否是可操作的Value,返回false表示为zero Value
Len适用于Array, Chan, Map, Slice, or String
MapIndex对map类型按key取值
MapKeysmap类型的所有key的列表
OverflowComplex溢出判断
OverflowFloat溢出判断
OverflowInt溢出判断
OverflowUint溢出判断
Pointer返回uintptr 适用于slice
Recvchan接收
Sendchan发送
Set将x赋值给v,类型要匹配
SetBoolBool赋值,需要先判断CanSet()为true
SetBytesBytes赋值
SetCapslice调整切片容量
SetMapIndexmap索引赋值
SetUintUnit赋值
SetPointerunsafe.Pointer赋值
SetStringString赋值
Slicereturn v[i:j] 适用于Array/Slict/String
Stringreturn value的string表示方法
TryRecvchan非阻塞接收
TrySendchan非阻塞发送
Type返回value的Type
UnsafeAddr返回指向value的data的指针

实例

结构体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
package main

import (
"fmt"
"reflect"
)

type Person struct {
Name string `json:"name"`
age int
}

func (p Person) GetName() string {
return p.Name
}
func (p Person) getAge() int {
return p.age
}
func main() {
// 结构体
tom := Person{Name: "tom", age: 20}
t := reflect.TypeOf(tom)
fmt.Println(t, t.Kind(), t.Size()) // 类型是Person,种类是struct,占据内存字节数
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
// 字段
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Println(i, f)
fmt.Println(
f.Name, // 字段名
f.Index, // 字段索引
f.Type, // 字段类型
f.Type.Kind(), // 字段类型的种类
f.Offset, // 相当于结构体首地址该字段值的偏移,string占16字节
f.Anonymous, // 是否匿名成员,就是没有名字。注意不要和可见性混淆
f.IsExported(), // 是否导出,包外可见否
f.Tag, // 本质上就是string类型
f.Tag.Get("json"), // 获取结构体字段定义后面反引号部分的tag
)
}
// 方法,只包括导出的方法,也不包括receiver是该结构体指针的方法
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Println(i, m)
fmt.Println(
m.Name, // 方法名
m.Index, // 方法索引
m.Type, // 方法类型,函数签名
m.Type.Kind(), // 方法类型的种类
m.IsExported(), // 是否导出。当然未导出的看不到
m.Func, // reflect.Value
)
}
}

指针类型

指针类型不能调用NumField()方法,这是结构体才能调用的,这时候就要使用Elem()方法来解析指针,相当于对指针类型变量做了*操作获取元素。

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
package main

import (
"fmt"
"reflect"
)

type Person struct {
Name string `json:"name"`
age int
}

func (p Person) GetName() string {
return p.Name
}
func (p Person) getAge() int {
return p.age
}
func main() {
// 结构体
tom := &Person{Name: "tom", age: 20}
t := reflect.TypeOf(tom)
fmt.Println(t, t.Kind()) // 类型是Person,种类是struct
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
// 字段
// fmt.Println(t.NumField()) //reflect: NumField of non-struct type *main.Person
for i := 0; i < t.Elem().NumField(); i++ {
f := t.Elem().Field(i)
fmt.Println(i, f)
fmt.Println(
f.Name, // 字段名
f.Index, // 字段索引
f.Type, // 字段类型
f.Type.Kind(), // 字段类型的种类
f.Offset, // 相当于结构体首地址该字段值的偏移,string占16字节
f.Anonymous, // 是否匿名成员,就是没有名字。注意不要和可见性混淆
f.IsExported(), // 是否导出,包外可见否
f.Tag, // 本质上就是string类型
f.Tag.Get("json"), // 获取结构体字段定义后面反引号部分的tag
)
}

// 方法,只包括导出的方法,也不包括receiver是该结构体指针的方法
for i := 0; i < t.Elem().NumMethod(); i++ {
m := t.Method(i)
fmt.Println(i, m)
fmt.Println(
m.Name, // 方法名
m.Index, // 方法索引
m.Type, // 方法类型,函数签名
m.Type.Kind(), // 方法类型的种类
m.IsExported(), // 是否导出。当然未导出的看不到
m.Func, // reflect.Value
)
}
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
// 方法,使用指针访问,可以访问所有receiver的导出的方法
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
fmt.Println(i, m)
fmt.Println(
m.Name, // 方法名
m.Index, // 方法索引
m.Type, // 方法类型,函数签名
m.Type.Kind(), // 方法类型的种类
m.IsExported(), // 是否导出。当然未导出的看不到
m.Func, // reflect.Value
)
}
}

接口类型

对于接口类型可以判断是否实现了某个接口。

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
package main

import (
"fmt"
"reflect"
)

type Runner interface {
run()
}
type Person struct {
Name string `json:"name"`
Age int
}
// func (Person) run() {
// }
func main() {
tom := Person{"Tom", 20}
t1 := reflect.TypeOf(tom)
t2 := reflect.TypeOf((*Runner)(nil)) // *Runner的类型
fmt.Println(t2, t2.Kind())
t3 := t2.Elem() // 解析指针获得实例的类型
fmt.Println(t3, t3.Kind())
if t1.Implements(t3) {
fmt.Println("实现了")
} else {
fmt.Println("未实现")
}
}

(*Runner)(nil) 说明:

nil是指针的零值,也就是空指针
Runner把空指针强制类型转换为Runner类型空指针

Value和原始值

reflect.Value 与原始值之间可以通过 值包装 和 值获取 相互转化。

方法签名说明
Interface() interface {}将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64将值以 int64 类型返回,所有有符号整型均可以此方式返回,如果需要的是int类型,则需要强制类型转换
Uint() uint64将值以 uint64 类型返回,所有无符号整型均可以此方式返回
Float() float64将值以双精度(float64)类型返回,所有浮点数(float32、float64)均可以此方式返回
Bool() bool将值以 bool 类型返回
Bytes() []bytes将值以字节数组 []bytes 类型返回
String() string将值以字符串类型返回
1
2
3
var a = 100
v := reflect.ValueOf(a) // 原始值 => Value,原始值包装
i := v.Interface() // 等价于 var i interface{} = (v's underlying value),Value => 原始值,值获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"reflect"
)

func main() {
var a = 100
v := reflect.ValueOf(a)
i := v.Interface() // 等价于 var i interface{} = (v's underlying value)
// 1 接口类型断言
j := i.(int) // 获得int类型原始值,断言失败panic
fmt.Printf("%T %[1]d\n", j)
j1, isStr := i.(string) // 断言失败与否看isStr,失败不panic
fmt.Println(j1, isStr)
// 2 值获取
k := v.Int() // 返回的是int64
fmt.Printf("%T %[1]d\n", k)
fmt.Println(int(k)) // 强制类型转换为int获得原始值
}

空值和有效性判断

方法签名说明
IsValid() bool判断值是否有效。当值本身非法时,返回 false,例如 reflect.ValueOf(nil).IsValid()就是false常用来判断返回值是否有效
IsZero() bool值是否是零值。如果值无效则panic
IsNil() bool值是否为 nil。必须是chan、func、interface、map、pointer、slice,否则panic。类似于语言层的 v== nil 操作常用来判断指针是否为空
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
package main

import (
"fmt"
"reflect"
)

func main() {
var a = 100
v := reflect.ValueOf(a)
fmt.Println(
v.IsValid(), // true
// v.IsNil(), // 必须是chan、func、interface、map、pointer、slice,否则panic
v.IsZero(), // false
)
var b *int
v = reflect.ValueOf(b)
fmt.Println(
v.IsValid(), // true
v.IsNil(), // true
v.IsZero(), // true
)

v = reflect.ValueOf(nil)
fmt.Println(
v.IsValid(), // false,因为nil是用来给某种类型做零值,直接用nil不知道其类型,所以无效
)
}

注意,上例中reflect.ValueOf(nil).IsValid()为false,而reflect.ValueOf(b).IsValid()为true,因为b是有类型的,它是*int不过是空指针罢了,所以有效,而nil不是。

反射和结构体

方法签名说明
Field(i int) Value根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时panic
NumField() int返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机
FieldByName(name string) Value根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体panic
FieldByIndex(index []int) Value多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体panic
Method(i int) Value根据索引,返回索引对应的结构体成员方法的反射值对象。当值不是结构体或索引超界时panic
MethodByName(name string) Value根据给定字符串返回字符串对应的结构体方法。没有找到时返回零值,当值不是结构体panic
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
package main

import (
"fmt"
"reflect"
)

type person struct {
name string
age int
}

func (p person) GetName() string {
return p.name
}
func main() {
var a = person{}
a.name = "tom"
// a.age = 100
v := reflect.ValueOf(a)
t := v.Type()
fmt.Println(v, t, t.Kind())
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
// 下面使用Type和Value遍历字段的区别
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Println(
f.Name, f.Index, f.Offset,
f.Anonymous, f.IsExported(), // Type字段类型信息
)
}
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
fmt.Println(i, f.IsValid(), f.IsZero()) // Value关注字段的值
}
fmt.Println("~~~~~~~~~~~~~~~~~~~~~~~~~~~")
fmt.Println(
v.FieldByName("name").IsZero(), // 通过v找底层结构体的name字段
v.Field(1).IsValid(), v.Field(1).IsZero(), // 字段age
v.FieldByIndex([]int{1}).IsValid(), // 字段age
v.FieldByName("score").IsValid(), // score字段不存在,无效
v.MethodByName("GetName").IsValid(), // GetName方法不存在,无效
)
}

反射调用函数

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
package main

import (
"fmt"
"reflect"
)

type person struct {
name string
age int
}

func (p person) GetName(prefix, suffix string) string {
return fmt.Sprintf("%s %s %s", prefix, p.name, suffix)
}
func main() {
var a = person{}
a.name = "tom"
// a.age = 100
v := reflect.ValueOf(a) // 结构体的Value
vf := v.MethodByName("GetName") // 函数的Value
fmt.Println(v, vf)
// 构建参数列表,需要2个入参
p1 := reflect.ValueOf("!!")
p2 := reflect.ValueOf("##")
inParams := []reflect.Value{p1, p2}
outParams := vf.Call(inParams) // Call调用需要[]reflect.Value的参数列表
fmt.Println(outParams)
}

反射修改值

方法签名说明
Elem() Value取值指向的元素值,类似于语言层 * 操作。当值类型不是指针或接口时panic,空指针时返回 nil 的 Value
Addr() Value对可寻址的值返回其地址,类似于语言层 & 操作。当值不可寻址时panic
CanAddr() bool表示值是否可寻址
CanSet() bool返回值能否被修改。要求值可寻址且是导出的字段
Set(x Value)将值设置为传入的反射值对象的值
Setlnt(x int64)使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机
SetUint(x uint64)使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64时分发生宕机
SetFloat(x float64)使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机
SetBool(x bool)使用 bool 设置值。当值的类型不是 bod 时会发生宕机
SetBytes(x []byte)设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机
SetString(x string)设置字符串值。当值的类型不是 string 时会发生宕机

如果CanSet() 返回false,调用以上Set*方法都会panic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 可被寻址
package main

import (
"fmt"
"reflect"
)

func main() {
var a int = 100
v := reflect.ValueOf(&a).Elem() // a指针指向的元素的Value
fmt.Println(v.CanAddr(), v.CanSet()) // true true
v.SetInt(200)
fmt.Println(v, int(v.Int()))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 可被导出
package main

import (
"fmt"
"reflect"
)

type person struct {
Name string
age int
}

func main() {
v := reflect.ValueOf(&person{Name: "Tom", age: 20})
vf1 := v.Elem().FieldByName("Name") // 导出字段
fmt.Println(vf1, vf1.CanAddr(), vf1.CanSet()) // true true
vf1.SetString("Jerry") // 成功修改
vf2 := v.Elem().FieldByName("age") // 未导出字段
fmt.Println(vf2, vf2.CanAddr(), vf2.CanSet()) // true false
// vf2.SetInt(30) // reflect.Value.SetInt using value obtained using unexported
fmt.Println(v) // 名字已经变成了Jerry了
}

反射创建实例

需要用到 reflect.New(typ reflect.Type) reflect.Value ,简单讲就是将Type NewValue

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"reflect"
)

func main() {
var a int = 100
t := reflect.TypeOf(a) // 提取类型信息
v := reflect.New(t) // 创建一个该类型的新的零值返回指针的Value,相当于new(int)
fmt.Println(v, v.Elem(), v.Type(), v.Kind()) // 内存地址 0 *int ptr
}
-------------本文结束感谢您的阅读-------------