反射
介绍
反射是应用程序动态检查其所拥有的结构,尤其是类型对象的一种能力。每种语言的反射模型都不同,有些语言根本不支持反射。
Go 语言实现了反射,反射机制就是在运行时动态调用对象的方法和属性,标准库的 reflect 包提供了相关的功能。
在 reflect 包中,通过 reflect.TypeOf() 和 reflect.ValueOf() 分别从类型、值的角度来描述一个 Go 对象
| func TypeOf(i interface{}) Type
type Type interface
func ValueOf(i interface{}) Value
type Value struct
|
在 Go 语言的实现中,一个接口类型的变量存储了两个信息,即一个 <值, 类型>
对 <value,type>
value 是实际变量值,type 是实际变量的类型,不能是接口类型。
两个简单的函数 reflect.TypeOf() 和 reflect.ValueOf(),返回被检查对象的类型和值
例如,x 被定义为: var x float64 = 3.4
,那么 reflect.TypeOf(x)
返回 float64,reflect.ValueOf(x)
返回 3.4。
实际上,反射通过检查一个接口类型变量从而得到该变量的类型成值,变量可以是任意类型(空接口)。这点从下面两个函数签名能够很明显地看出来
| func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
|
在 Go 语言中实现反射,是将 "接口类型变量" 转换为 "反射类型对象"。
上面函数中 i 就是接口类型变量,而返回值就是 reflect.Type 和 reflect.Value。
在实际应用中,i 一般是一个具体类型的变量,因为 i 对应一个空接口类型;而反射正好是将接口类型转为反射类型,得到反射类型后就可以做更多的事情了
所以,当调用 reflect.TypeOf(x)
时,x 被当作一个接口变量处理,然后 reflect.TypeOf() 对接口变量进行拆解,返回其反射类型信息
reflect.Type 和 reflect.Value 还有许多其他检查和操作方法
Type 的主要方法有:
Kind()
: 返回一个常量,表示具体类型的底层类型
Elem()
: 返回指针、数组、切片、字典、通道等类型。这个方法要慎用,如果用于其他类型会出现运行时异常
Value 的主要方法有:
Kind()
: 返回一个常量,表示具体类型的底层类型
Type()
: 返回具体类型所对应的 reflect.Type(静态类型)
反射让我们可以在程序运行时动态检测类型和变量,例如它的值、方法和类型,甚至可以在运行时修改和创建变量、函数和结构。这对于没有源代码的包尤其有用
反射是一个强大的工具,但对性能有一定的影响,因此除非有必要,应当避免使用或小心使用。
下面代码针对 int、自定义类型、数组以及结构体分别使用反射机制,其中的差异请看代码注释
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 | package main
import (
"fmt"
"reflect"
)
type Student struct {
name string
}
type MyInt int
func main() {
var a int = 9
v := reflect.ValueOf(a) // 返回 Value 类型对象,值为 9
t := reflect.TypeOf(a) // 返回 Type 类型对象,值为 int
fmt.Println(v, t, v.Type(), v.Kind(), t.Kind()) // Kind() 返回底层基础类型
var mi MyInt = 99
mv := reflect.ValueOf(mi) // 返回 Value 类型对象,值为 99
mt := reflect.TypeOf(mi) // 返回 Type 类型对象,值为 MyInt
fmt.Println(mv, mt, mv.Type(), mv.Kind(), mt.Kind()) // Kind() 返回底层基础类型
var b [5]int = [5]int{5, 6, 7, 8}
fmt.Println(reflect.TypeOf(b), reflect.TypeOf(b).Kind(), reflect.TypeOf(b).Elem()) // [5]int array int
var Pupil Student
p := reflect.ValueOf(Pupil) // 使用 ValueOf 获得结构体的 Value 对象
fmt.Println(p.Type()) // 输出: Student
fmt.Println(p.Kind()) // 输出: struct
}
|
输出:
| 9 int int int int
99 main.MyInt main.MyInt int int
[5]int array int
main.Student
struct
|
在 Go 语言中,静态类型就是变量声明时赋予的类型,也就是在反射中的 reflect.Type 对应的值,而 kind() 对应的是基础类型
Kind() 大概会返回切片、字典、指针、结构体、接口、字符串、数组、函数、整型或其他基础类型。
如上面代码中 Kind() 返回结构体: fmt.Println(p.Kind())
,而 Type() 返回静态类型名 Student: fmt.Println(p.Type())
。
MyInt 是静态类型,而 int 是它的基础类型
Type() 返回的是静态类型,而 kind() 返回的是基础类型
类型
反射(reflect)让我们能在运行期探知对象的类型信息和内存结构,这从一定程度上弥补了静态语言在动态行为上的不足。
同时,反射还是实现元编程的重要手段
和 C 数据结构一样,Go 对象头部并没有类型指针,通过其自身是无法在运行期获知任何类型相关信息的。
反射操作所需的全部信息都源自接口变量。
接口变量除存储自身类型外,还会保存实际对象的类型数据
| func TypeOf(i interface{}) Type
func ValueOf(i interface{}) Value
|
这两个反射入口函数,会将任何传入的对象转换为接口类型。
在面对类型时,需要区分 Type 和 Kind。
前者表示真实类型(静态类型),后者表示其基础结构(底层类型)类别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | package main
import (
"fmt"
"reflect"
)
type X int
func main() {
var a X = 100
t := reflect.TypeOf(a)
fmt.Println(t.Name(), t.Kind())
}
|
输出:
所以在类型判断上,须选择正确的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | package main
import (
"fmt"
"reflect"
)
type X int
type Y int
func main() {
var a, b X = 100, 200
var c Y = 300
ta, tb, tc := reflect.TypeOf(a), reflect.TypeOf(b), reflect.TypeOf(c)
fmt.Println(ta == tb, ta == tc)
fmt.Println(ta.Kind() == tc.Kind())
}
|
输出:
除通过实际对象获取类型外,也可直接构造一些基础复合类型
1
2
3
4
5
6
7
8
9
10
11
12
13 | package main
import (
"fmt"
"reflect"
)
func main() {
a := reflect.ArrayOf(10, reflect.TypeOf(byte(0)))
m := reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf(0))
fmt.Println(a, m)
}
|
输出:
传入对象应区分基类型和指针类型,因为它们并不属于同一类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | package main
import (
"fmt"
"reflect"
)
func main() {
x := 100
tx, tp := reflect.TypeOf(x), reflect.TypeOf(&x)
fmt.Println(tx, tp, tx == tp)
fmt.Println(tx.Kind(), tp.Kind())
fmt.Println(tx == tp.Elem())
}
|
输出:
| int *int false
int ptr
true
|
方法 Elem 返回指针、数组、切片、字典(值)或通道的基类型
| package main
import (
"fmt"
"reflect"
)
func main() {
fmt.Println(reflect.TypeOf(map[string]int{}).Elem())
fmt.Println(reflect.TypeOf([]int32{}).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 | package main
import (
"fmt"
"reflect"
)
type user struct {
name string
age int
}
type manager struct {
user
title string
}
func main() {
var m manager
t := reflect.TypeOf(&m)
if t.Kind() == reflect.Ptr { // 获取指针的基类型
t = t.Elem()
}
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Println(f.Name, f.Type, f.Offset)
if f.Anonymous { // 输出匿名字段结构
for x := 0; x < f.Type.NumField(); x++ {
af := f.Type.Field(x)
fmt.Println(af.Name, af.Type)
}
}
}
}
|
输出:
| user main.user 0
name string
age int
title string 24
|
对于匿名字段,可用于多级索引(按定义顺序)直接访问
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 | package main
import (
"fmt"
"reflect"
)
type user struct {
name string
age int
}
type manager struct {
user
title string
}
func main() {
var m manager
t := reflect.TypeOf(m)
name, _ := t.FieldByName("name") // 按名称查找
fmt.Println(name.Name, name.Type)
age := t.FieldByIndex([]int{0, 1}) // 按多级索引查找
fmt.Println(age.Name, age.Type)
}
|
输出:
FieldByName 不支持多级名称,如有同名遮蔽,须通过匿名字段二次获取
值
和 Type 获取类型信息不同,Value 专注于对象实例数据读写
接口变量会复制对象,且是 unaddressable 的,所以要想修改目标对象,就必须使用指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | package main
import (
"fmt"
"reflect"
)
func main() {
a := 100
va, vp := reflect.ValueOf(a), reflect.ValueOf(&a).Elem()
fmt.Println(va.CanAddr(), va.CanSet())
fmt.Println(vp.CanAddr(), vp.CanSet())
}
|
输出:
就算传入指针,一样需要通过 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 | package main
import (
"fmt"
"reflect"
"unsafe"
)
type User struct {
Name string
code int
}
func main() {
p := new(User)
v := reflect.ValueOf(p).Elem()
name := v.FieldByName("Name")
code := v.FieldByName("code")
fmt.Printf("name: canaddr==%v,canset=%v\n", name.CanAddr(), name.CanSet())
fmt.Printf("code: canaddr==%v,canset=%v\n", code.CanAddr(), code.CanSet())
if name.CanSet() {
name.SetString("Tom")
}
if code.CanAddr() {
*(*int)(unsafe.Pointer(code.UnsafeAddr())) = 100
}
fmt.Printf("%+v\n", *p)
}
|
输出:
| name: canaddr==true,canset=true
code: canaddr==true,canset=false
{Name:Tom code:100}
|
方法
动态调用方法,谈不上有多麻烦。只需按 In 列表准备好所需参数即可
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 X struct{}
func (X) Test(x, y int) (int, error) {
return x + y, fmt.Errorf("err: %d", x+y)
}
func main() {
var a X
v := reflect.ValueOf(&a)
m := v.MethodByName("Test")
in := []reflect.Value{
reflect.ValueOf(1),
reflect.ValueOf(2),
}
out := m.Call(in)
for _, v := range out {
fmt.Println(v)
}
}
|
输出:
对于变参来说,用 CallSlice 要更方便一些
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 | package main
import (
"fmt"
"reflect"
)
type X struct{}
func (X) Format(s string, a ...interface{}) string {
return fmt.Sprintf(s, a...)
}
func main() {
var a X
v := reflect.ValueOf(&a)
m := v.MethodByName("Format")
out := m.Call([]reflect.Value{
reflect.ValueOf("%s = %d"),
reflect.ValueOf("x"), // 所有参数都须处理
reflect.ValueOf(100),
})
fmt.Println(out)
out = m.CallSlice([]reflect.Value{
reflect.ValueOf("%s = %d"),
reflect.ValueOf([]interface{}{"x", 100}), // 仅一个 []interface{} 即可
})
fmt.Println(out)
}
|
输出:
无法调用非导出方法,甚至无法获取有效地址