反射
# 反射
学习 Go 反射,可以查看
reflect里的各种注释,还可以跟着$GOROOT/src/reflect/all_test.go测试文件尝试反射的各种用法。相关代码链接:https://github.com/xuqil/learning-go/tree/base/base/ch12
绝大多数编程语言的类型系统都是类似的,会有声明类型、实际类型之类的分别。
在 Go 反射里,一个实例可以看成两个部分:
- 值(
reflect.Value) - 实际类型(
reflect.Type)
# reflect.Kind
Kind是一个枚举值,用来判断操作的对应类型,例如是否是指针、是否是数组、是否是切片等。
reflect/type.go
// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint
const (
Invalid Kind = iota
Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
Array
Chan
Func
Interface
Map
Pointer
Slice
String
Struct
UnsafePointer
)
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
例如,判断一个常量的反射类型:
reflect_test.go
func TestReflect_Kind(t *testing.T) {
var age int8 = 18
typ := reflect.TypeOf(age)
switch typ.Kind() {
case reflect.Int8:
fmt.Println("age的类型:", reflect.Int8)
default:
fmt.Println("age的类型:", typ.Kind().String())
}
}
2
3
4
5
6
7
8
9
10
# reflect.Type和reflect.Value
反射的相关 API 都在reflect包,最核心的两个:
reflect.Value:用于操作值,部分值时可以被反射修改的reflect.Type:用于操作类型信息,类型信息只能被读取
注意:reflect.Type可以通过reflect.Value得到,但是反过来则不行。
# reflect.Type
一个reflect.Type表示一个 Go 类型。它是一个接口,有许多方法来区分类型以及检查它们的组成部分,例如一个结构体的成员或一个函数的参数等。reflect.Type体现的是动态类型。
reflect/Type.go
type Type interface {
...
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
Name() string
PkgPath() string
...
String() string
// Kind returns the specific kind of this type.
Kind() Kind
...
// Elem returns a type's element type.
// It panics if the type's Kind is not Array, Chan, Map, Pointer, or Slice.
Elem() Type
// Field returns a struct type's i'th field.
// It panics if the type's Kind is not Struct.
// It panics if i is not in the range [0, NumField()).
Field(i int) StructField
// FieldByIndex returns the nested field corresponding
// to the index sequence. It is equivalent to calling Field
// successively for each index i.
// It panics if the type's Kind is not Struct.
FieldByIndex(index []int) StructField
// FieldByName returns the struct field with the given name
// and a boolean indicating if the field was found.
FieldByName(name string) (StructField, bool)
FieldByNameFunc(match func(string) bool) (StructField, bool)
...
// Key returns a map type's key type.
// It panics if the type's Kind is not Map.
Key() Type
Len() int
// NumField returns a struct type's field count.
// It panics if the type's Kind is not Struct.
NumField() int
}
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
可以通过reflect.TypeOf和Value.Type拿到Type:
typ := reflect.TypeOf(3) // a reflect.Type
fmt.Println(typ.String()) // "int"
fmt.Println(typ) // "int"
typ2 := reflect.TypeOf(int8(3)) // a reflect.Type
fmt.Println(typ2.String()) // "int8"
fmt.Println(typ2) // "int8"
2
3
4
5
6
7
其中TypeOf(3)调用将值 3 传给interface{}参数。将一个具体的值转为接口类型会有一个隐式的接口转换操作,它会创建一个包含两个信息的接口值:操作数的动态类型(这里是int)和它的动态的值(这里是 3)。
因为reflect.TypeOf返回的是一个动态类型的接口值,它总是返回具体的类型。因此,下面的代码将打印"*os.File"而不是"io.Writer"。
var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File"
2
# reflect.Value
一个reflect.Value可以装载任意类型的值。函数reflect.ValueOf接受任意的any类型,并返回一个装载着其动态值的reflect.Value。和reflect.TypeOf类似,reflect.ValueOf返回的结果也是具体的类型,但是reflect.Value也可以持有一个接口值。
val := reflect.ValueOf(3) // a reflect.Value
fmt.Println(val) // "3"
fmt.Printf("%v\n", val) // "3"
fmt.Println(val.String()) // "<int Value>"
val2 := reflect.ValueOf(int8(3)) // a reflect.Value
fmt.Println(val2) // "3"
fmt.Printf("%v\n", val2) // "3"
fmt.Println(val2.String()) // "<int8 Value>"
2
3
4
5
6
7
8
9
reflect.ValueOf的逆操作是reflect.Value.Interface方法。它返回一个any类型,装载着与reflect.Value相同的具体值:
val := reflect.ValueOf(3) // a reflect.Value
x := val.Interface() // an interface{}
i := x.(int) // an int
fmt.Printf("%d\n", i) // "3"
val2 := reflect.ValueOf(int8(3)) // a reflect.Value
x2 := val2.Interface() // an interface{}
i2 := x2.(int8) // an int8
fmt.Printf("%d\n", i2) // "3"
2
3
4
5
6
7
8
9
此处注意需要通过类型断言来访问内部的值。
对Value调用Type方法将返回具体类型所对应的reflect.Type:
val := reflect.ValueOf(3) // a reflect.Value
typ := val.Type() // a reflect.Type
fmt.Println(typ.String()) // "int"
val2 := reflect.ValueOf(int8(3)) // a reflect.Value
typ2 := val2.Type() // a reflect.Type
fmt.Println(typ2.String()) // "int8"
2
3
4
5
6
7
# 遍历基础类型
无论是基础类型,还是结构体,我们都可以通过reflect.Type和reflect.Value来分别取的它们的类型和值。
下面示例是获取基础类型string的类型和值,
func TestReflect_String(t *testing.T) {
var name = "Jerry"
nTyp := reflect.TypeOf(name)
nVal := reflect.ValueOf(name)
// string->type: string value: Jerry
fmt.Printf("%s->type: %s value: %s\n", nTyp.String(), nTyp.Kind().String(), nVal.Interface())
}
2
3
4
5
6
7
8
如果reflct.ValueOf传入的参数是指针,需要通过Elem方法来获取指针指向的值:
func TestReflect_StringPtr(t *testing.T) {
var name = "Jerry"
nTyPtr := reflect.TypeOf(&name)
nValPtr := reflect.ValueOf(&name)
// *string->type: ptr value: 0xc0000745e0
fmt.Printf("%s->type: %s value: %v\n", nTyPtr.String(), nTyPtr.Kind().String(), nValPtr.Interface())
// *string->type: ptr value: Jerry
fmt.Printf("%s->type: %s value: %v\n", nTyPtr.String(), nTyPtr.Kind().String(), nValPtr.Elem().Interface())
}
2
3
4
5
6
7
8
9
10
11
# 遍历结构体
对于基础类型,可以直接通过reflect.Type的方法拿到它类型:
对于结构体类型,可以通过Type的NumField()方法取得结构体字段的数量,然后根据下标从Field(i int)方法取得字段的信息,Field方法的返回值为StructField结构体。此外,还可以通过FieldByName方法,传入结构体字段的名称来取得字段的信息。
# 遍历结构体字段
遍历普通结构体
func TestReflect_StructField(t *testing.T) { type ReflectUser struct { Name string Age int } ru := ReflectUser{ Name: "Tom", Age: 18, } IterateFields(ru) } func IterateFields(entity any) { typ := reflect.TypeOf(entity) val := reflect.ValueOf(entity) for i := 0; i < typ.NumField(); i++ { ft := typ.Field(i) fv := val.Field(i) fmt.Printf("%s.%s 的类型: %s 值: %v\n", typ.Name(), ft.Name, ft.Type.String(), fv.Interface()) } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23打印结果:
ReflectUser.Name 的类型: string 值: Tom ReflectUser.Age 的类型: int 值: 181
2遍历带私有变量的结构体
给
ReflectUser结构体新增一个不可导出变量phone:type ReflectUser struct { Name string Age int phone string } ru := ReflectUser{ Name: "Tom", Age: 18, phone: "666", }1
2
3
4
5
6
7
8
9
10
11再次运行程序,发生
panic了:panic: reflect.Value.Interface: cannot return value obtained from unexported field or method [recovered] panic: reflect.Value.Interface: cannot return value obtained from unexported field or method1
2由于
phone字段是unexported类型,所以不能获取phone的值。新增一个
unexported类型判断,如果是unexported,就取它的零值,可以使用func Zero(Type) Value函数生成带零值的Value:func IterateFields(entity any) { typ := reflect.TypeOf(entity) val := reflect.ValueOf(entity) for i := 0; i < typ.NumField(); i++ { ft := typ.Field(i) fv := val.Field(i) if ft.IsExported() { fmt.Printf("%s.%s 的类型: %s 值: %v\n", typ.Name(), ft.Name, ft.Type.String(), fv.Interface()) } else { fmt.Printf("%s.%s 的类型: %s 值: %q\n", typ.Name(), ft.Name, ft.Type.String(), reflect.Zero(ft.Type).Interface()) } } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15打印结果:
ReflectUser.Name 的类型: string 值: Tom ReflectUser.Age 的类型: int 值: 18 ReflectUser.phone 的类型: string 值: ""1
2
3零值结构体
如果结构体传入的为
nil结构体,就不能拿到对应的字段了。可以通过func (v Value) IsZero() bool方法判断是否为零值结构体。func TestReflect_StructField(t *testing.T) { // ... var ru = (*ReflectUser)(nil) IterateFields(ru) } func IterateFields(entity any) { // ... if val.IsZero() { fmt.Println("不支持零值结构体") return } // ... }1
2
3
4
5
6
7
8
9
10
11
12
13此外,还有个
func (v Value) IsNil() bool方法,但它只能判断chan, func, interface, map, pointer, or slice value是否为nil。遍历结构体指针
这里,我们尝试着向
IterateFields方法传入一个结构体指针:IterateFields(&ru)1运行程序后,发生
panic了:panic: reflect: NumField of non-struct type *ch12.ReflectUser [recovered] panic: reflect: NumField of non-struct type *ch12.ReflectUser1
2从
reflect.Type接口里的NumField方法的注释里可以看出,如果typ的Kind不是Struct就会发生panic:// NumField returns a struct type's field count. // It panics if the type's Kind is not Struct. NumField() int1
2
3我们传入的参数是
&ru,即指针类型,所以会发生panic,现在只需要修改一下IterateFields函数,让typ的Kind不是reflect.Ptr:func IterateFields(entity any) { // ... for typ.Kind() == reflect.Ptr { // 拿到指针指向的对象 typ = typ.Elem() val = val.Elem() } if typ.Kind() != reflect.Struct { fmt.Println("不是结构体类型") return } // ... }1
2
3
4
5
6
7
8
9
10
11
12
13Type的Elem()方法返回Type的元素类型;Value的Elem()方法返回Value指针指向的Value,我们可以通过遍历的操作,拿到reflect.Ptr的元素类型。type Type interface { // ... // Elem returns a type's element type. // It panics if the type's Kind is not Array, Chan, Map, Pointer, or Slice. Elem() Type // ... } // Elem returns the value that the interface v contains // or that the pointer v points to. // It panics if v's Kind is not Interface or Pointer. // It returns the zero Value if v is nil. func (v Value) Elem() Value1
2
3
4
5
6
7
8
9
10
11
12
13遍历嵌套结构体
例如
ReflectUser嵌套了一个*Address:type Address struct { city string } type ReflectUser struct { Name string Age int phone string address *Address }1
2
3
4
5
6
7
8
9
10这时候需要用到递归遍历,可以参考《Go 语言圣经》的“Display递归打印” (opens new window)篇章。
# 遍历结构体方法
结构体的方法信息可以通过NumMethod和Method两个方法遍历得到,也可以使用MethodByName方法根据方法名称直接取得;其中Method和MethodByName返回的都是Method结构体。
// Method represents a single method.
type Method struct {
// Name is the method name.
Name string
PkgPath string
Type Type // method type
Func Value // func with receiver as first argument
Index int // index for Type.Method
}
type Type interface {
// ...
Method(int) Method
Method(int) Method
MethodByName(string) (Method, bool)
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
下面的例子,通过遍历结构体和结构体指针取得对应的方法,并调用方法获取返回值。
types/user.go
package types
import "fmt"
type User struct {
Name string
Age int
phone string
}
func NewUser(name string, age int, phone string) User {
return User{
Name: name,
Age: age,
phone: phone,
}
}
func NewUserPtr(name string, age int, phone string) *User {
return &User{
Name: name,
Age: age,
phone: phone,
}
}
func (u User) GetAge() int {
return u.Age
}
func (u User) GetPhone() string {
return u.phone
}
func (u *User) ChangeName(newName string) {
u.Name = newName
}
func (u User) private() {
fmt.Println("private")
}
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
methods.go
package ch12
import (
"fmt"
"reflect"
)
func IterateFunc(entity any) {
typ := reflect.TypeOf(entity)
numMethod := typ.NumMethod()
fmt.Println(typ.String(), "方法的个数:", numMethod)
for i := 0; i < numMethod; i++ {
method := typ.Method(i)
fn := method.Func // 拿到结构体方法的 Value,需要注意的是,方法的第一个参数为 receiver
numIn := fn.Type().NumIn() // 方法参数的个数
inputTypes := make([]reflect.Type, 0, numIn) // 每个参数的类型
inputValues := make([]reflect.Value, 0, numIn) //每个入参的零值
inputValues = append(inputValues, reflect.ValueOf(entity)) // 第一个参数为 receiver
inputTypes = append(inputTypes, reflect.TypeOf(entity)) // 第一个入参为 receiver
paramTypes := fmt.Sprintf("%s", typ.String())
// 这个遍历是为了得到方法的各个参数类型,以及参数对应的零值
for j := 1; j < numIn; j++ {
fnInType := fn.Type().In(j)
inputTypes = append(inputTypes, fnInType) // append 参数的类型
inputValues = append(inputValues, reflect.Zero(fnInType)) // append 入参的零值
paramTypes = paramTypes + "," + fnInType.String()
}
returnTypes := ""
numOut := fn.Type().NumOut() // 返回值的个数
outputTypes := make([]reflect.Type, 0, numOut)
// 拿到每个返回值的类型
for j := 0; j < numOut; j++ {
fnOutType := fn.Type().Out(j)
outputTypes = append(outputTypes, fnOutType)
if j > 0 {
returnTypes += ","
}
returnTypes += fnOutType.String()
}
resValues := fn.Call(inputValues) // 调用结构体里的方法,Call 的参数为方法入参的切片,返回值存储在切片
result := make([]any, 0, len(resValues))
for _, v := range resValues {
result = append(result, v.Interface())
}
fSign := fmt.Sprintf("func(%s) %s", paramTypes, returnTypes)
fmt.Println("方法签名:", fSign)
fmt.Printf("调用方法: %s 入参: %v 返回结果: %v\n", method.Name, inputValues, result)
}
}
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
User有三个公开的方法GetAge、GetPhone和ChangeName以及一个私有方法private,其中GetAge、GetPhone和private的receiver都为User,而ChangeName的receiver为*User。不同的receiver有不同的方法集,指针类型的receiver拥有非指针类型receiver的方法集,而非指针类型的receiver不会包含指针类型receiver的方法集。
需要额外注意的一点是,方法的第一个参数实际上是它的receiver:
func TestReflect_Method(t *testing.T) {
u := types.NewUser("Tom", 18, "666")
f := types.User.GetAge
fmt.Printf("%T\n", u.GetAge) // func() int
fmt.Printf("%T\n", f) // func(types.User) int
fmt.Printf("%T\n", (*types.User).GetAge) //func(*types.User) int
// 下面两条语句是等价的
fmt.Println(u.GetAge()) // 18
fmt.Println(f(u)) // 18
}
2
3
4
5
6
7
8
9
10
11
测试IterateFunc:
传入普通结构体
func TestIterateFunc(t *testing.T) { user := types.NewUser("Tom", 18, "666") IterateFunc(user) }1
2
3
4执行结果:
types.User 方法的个数: 2 方法签名: func(types.User) int 调用方法: GetAge 入参: [<types.User Value>] 返回结果: [18] 方法签名: func(types.User) string 调用方法: GetPhone 入参: [<types.User Value>] 返回结果: [666]1
2
3
4
5传入指针结构体
func TestIterateFunc(t *testing.T) { user := types.NewUserPtr("Tom", 18, "666") IterateFunc(user) }1
2
3
4执行结果:
*types.User 方法的个数: 3 方法签名: func(*types.User,string) 调用方法: ChangeName 入参: [<*types.User Value> ] 返回结果: [] 方法签名: func(*types.User) int 调用方法: GetAge 入参: [<*types.User Value>] 返回结果: [18] 方法签名: func(*types.User) string 调用方法: GetPhone 入参: [<*types.User Value>] 返回结果: [666]1
2
3
4
5
6
7
从结果可以看到私有方法是拿不到的,也是不能执行的。
# 通过reflect.Value修改值
可以通过Value的CanSet方法判断Value是否可被修改;调用Value的Set方法可以给变量重新设置一个新的值。值得注意的是,只有addressable的Value才可以被修改,例如指针;此外如果变量的Type为unexported也是不可修改的。
// CanSet reports whether the value of v can be changed.
// A Value can be changed only if it is addressable and was not
// obtained by the use of unexported struct fields.
// If CanSet returns false, calling Set or any type-specific
// setter (e.g., SetBool, SetInt) will panic.
func (v Value) CanSet() bool
// Set assigns x to the value v.
// It panics if CanSet returns false.
// As in Go, x's value must be assignable to v's type.
func (v Value) Set(x Value)
2
3
4
5
6
7
8
9
10
11
# 修改基础类型的值
尝试修改基础类型的值
func TestReflect_SetValue(t *testing.T) { var Name = "Tom" fmt.Println("Name before:", Name) val := reflect.ValueOf(Name) val.Set(reflect.ValueOf("Jerry")) fmt.Println("Name after:", Name) }1
2
3
4
5
6
7执行程序,发生
panic了:panic: reflect: reflect.Value.Set using unaddressable value [recovered] panic: reflect: reflect.Value.Set using unaddressable value1
2这里传给
reflect.ValueOf的是string类型,生成的是unaddressable的Value,因此是无法Set的。可以通过CanSet方法判断是否可以Set设置新值。func TestReflect_SetValue(t *testing.T) { var Name = "Tom" fmt.Println("Name before:", Name) val := reflect.ValueOf(Name) if !val.CanSet() { fmt.Println("Name不可被修改") } else { val.Set(reflect.ValueOf("Jerry")) } fmt.Println("Name after:", Name) }1
2
3
4
5
6
7
8
9
10
11打印的结果,变量
Name并没有被修改:Name before: Tom Name不可被修改 Name after: Tom1
2
3传入指针,修改指针的值
func TestReflect_SetValue(t *testing.T) { var Name = "Tom" fmt.Println("Name before:", Name) val := reflect.ValueOf(&Name) fmt.Println(val.Type().Kind()) // "ptr" if !val.CanSet() { fmt.Println("Name不可被修改") } else { val.Set(reflect.ValueOf("Jerry")) } fmt.Println("Name after:", Name) }1
2
3
4
5
6
7
8
9
10
11
12输出的结果,变量
Name并没有被修改:Name before: Tom ptr Name不可被修改 Name after: Tom1
2
3
4因为
reflect.ValueOf(&Name)只是reflect.Ptr类型,指向的是一个&Name,稍微修改下代码:val := reflect.ValueOf(&Name) val = val.Elem() fmt.Println(val.Type().Kind()) // "string"1
2
3执行后输出的结果,
Name的值被修改了:Name before: Tom string Name after: Jerry1
2
3
# 修改结构体的值
跟基础类型一样,也是通过CanSet判断字段是否可被修改,如果可以修改,可以使用Set方法进行赋值。
type ReflectUser struct {
Name string
Age int
phone string
}
func (r ReflectUser) String() string {
return fmt.Sprintf("Name: %s Age: %d phone: %s",
r.Name, r.Age, r.phone)
}
func TestReflect_StructSetValue(t *testing.T) {
ru := ReflectUser{
Name: "Tom",
Age: 18,
phone: "666",
}
fmt.Println("before:", ru)
SetField(&ru, "Name", "Jerry")
SetField(&ru, "Age", 20)
SetField(&ru, "phone", "888") // unexported 字段不可修改
fmt.Println("after:", ru)
}
func SetField(entity any, field string, newValue any) {
val := reflect.ValueOf(entity)
for val.Type().Kind() == reflect.Pointer {
val = val.Elem()
}
fv := val.FieldByName(field)
if !fv.CanSet() {
fmt.Println("为不可修改字段")
return
}
fv.Set(reflect.ValueOf(newValue))
}
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
从执行后的结果看,可导出字段被修改了,不可导出字段没有被修改:
before: Name: Tom Age: 18 phone: 666
phone 为不可修改字段
after: Name: Jerry Age: 20 phone: 666
2
3