反射
# 反射
学习 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 值: 18
1
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 method
1
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.ReflectUser
1
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() int
1
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() Value
1
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 value
1
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: Tom
1
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: Tom
1
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: Jerry
1
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