本文总阅读量 次
什么是反射
反射指程序在运行时(runtime)可以访问,检测和修改自身状态或行为的能力。
反射的本质是在运行时候动态的获取一个变量的类型信息和值信息。
Golang 中反射可以实现以下功能:
反射可以在程序运行期间动态的获取变量的各种信息,比如变量的类型 类别
如果是结构体,通过反射还可以获取结构体本身的信息,比如结构体的字段、结构体的
方法、结构体的 tag。
通过反射,可以修改变量的值,可以调用关联的方法
在 GoLang 的反射机制中,任何接口值都由是一个具体类型和具体类型的值两部分组成的。
在 GoLang 中,反射的相关功能由内置的 reflect 包提供,任意接口值在反射中都可以理解为
由 reflect.Type 和 reflect.Value 两 部 分 组 成 , 并 且 reflect 包 提 供 了 reflect.TypeOf 和
reflect.ValueOf 两个重要函数来获取任意对象的 Value 和 Type。
reflect.TypeOf()获取任意值的类型对象
1
func TypeOf ( i interface {}) Type
从方法可以看出接受参数是空接口类型,可以获得任意值的类
型对象(reflect.Type),程序通过类型对象可以访问任意值的类型信息。
1
2
3
4
5
6
7
8
9
10
func main () {
var a int = 32
var b float64 = 3.14
// reflect.TypeOf() 接受一个空接口类型,返回一个reflect.Type 类型
typeA := reflect . TypeOf ( a )
fmt . Println ( typeA )
typeB := reflect . TypeOf ( b )
fmt . Println ( typeB )
}
输出:
int
float64
type Name 和 type Kind 方法
使用reflect.Typeof()返回的是一个reflect.Type接口
在反射中关于类型还划分为两种:类型(Type)和种类(Kind)。因为在 Go 语言中我们可
以使用 type 关键字构造很多自定义类型,而种类(Kind)就是指底层的类型,但在反射中,
当需要区分指针、结构体等大品种的类型时,就会用到种类(Kind)。 举个例子,我们定
义了两个指针类型和两个结构体类型,通过反射查看它们的类型和种类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type myInt int
type Student struct {
Name string `json:"name"`
Age int `json:"age"`
}
func reflectType ( x interface {}) {
typ := reflect . TypeOf ( x )
fmt . Printf ( "类型是:%v ,名字是 %#v 底层种类是:%#v\n" , typ , typ . Name (), typ . Kind (). String ())
}
func main () {
var s Student
var a myInt
reflectType ( s ) //类型是:main.Student ,名字是 "Student" 底层种类是:"struct"
reflectType ( a ) //类型是:main.myInt ,名字是 "myInt" 底层种类是:"int"
reflectType ( & s ) //类型是:*main.Student ,名字是 "" 底层种类是:"ptr"
reflectType ( & a ) //类型是:*main.myInt ,名字是 "" 底层种类是:"ptr"
}
//数组,map,切片,指针的Name()返回是空
在reflect包中kind的定义如下
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
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
)
reflect.ValueOf()函数
reflect.ValueOf()返回的是 reflect.Value 类型,其中包含了原始值的值信息。reflect.Value 与原
始值之间可以互相转换。
reflect.Value 类型提供的获取原始值的方法如下:
如何将一个原始值转为Value类型?
1
value := refect . ValueOf ( 10 ) //将int类型的10转为Value类型
例子:refelct.Value和原始值的转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main () {
var a int64 = 33
var b float64 = 3.14
reflectValue ( a )
reflectValue ( b )
v := reflect . ValueOf ( 10 )
f := reflect . ValueOf ( 3.14 )
fmt . Println ( v . Kind ()) //int
fmt . Println ( f . Kind ()) //float64
}
func reflectValue ( x interface {}) {
val := reflect . ValueOf ( x ) //ValueOf返回一个初始化为接口i中存储的具体值的Value结构体。ValueOf(nil)返回零值。
// val是reflect.Value类型不能直接和具体类型进行运算,要使用Kind()方法判断
kind := val . Kind () // 获取值的类型
switch kind {
case reflect . Int64 :
fmt . Println ( val . Int () + 6 )
case reflect . Float64 :
fmt . Println ( val . Float () + 5.3 )
}
}
通过反射修改变量的值
注意点:要想通过反射修改变量的值,传递的必须是变量的地址,再利用Elem()方法获取变量进行设置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main () {
var a = 3.14
reflectSetValue ( & a )
fmt . Println ( a )
}
func reflectSetValue ( x interface {}) {
val := reflect . ValueOf ( x )
switch val . Elem (). Kind () {
case reflect . Int :
val . Elem (). SetInt ( 50 )
case reflect . Float64 :
val . Elem (). SetFloat ( 6.28 )
}
}
结构体反射
结构体反射应用的比较多,比如读取一个文件的配置信息然后赋值给结构体。
与结构体相关的方法
任意值通过 reflect.TypeOf()获得反射对象信息后,如果它的类型是结构体,可以通过反射值
对象(reflect.Type)的 NumField()和 Field()方法获得结构体成员的详细信息
返回的refelct.StructField定义如下
1
2
3
4
5
6
7
8
9
type StructField struct {
Name string // 字段的名称
PkgPath string // PkgPath 非导出字段的包路径,对导出字段为""
Type Type // 字段的类型
Tag StructTag // 字段的Tag标签
Offset uintptr
Index [] int
Anonymous bool // 是否是匿名字段
}
相关方法如下:
修改结构体数据例子
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
func main () {
var s = Student {}
reflectStruct ( & s )
fmt . Println ( s )
}
func reflectStruct ( x interface {}) error {
structTyp := reflect . TypeOf ( x )
v := reflect . ValueOf ( x )
// 判断类型是否是结构体
if structTyp . Kind () != reflect . Struct && structTyp . Elem (). Kind () != reflect . Struct {
return errors . New ( "expect struct " )
}
//修改结构体数据
//structField, _ := structTyp.Elem().FieldByName("Name")
//tag := structField.Tag.Get("info")
name := v . Elem (). FieldByName ( "Name" )
name . SetString ( "王五" )
age := v . Elem (). FieldByName ( "Age" )
age . SetInt ( 23 )
return nil
}
例子:从文件info.txt读取内容,给结构体赋值
info.txt内容为
1
2
3
name=小明
age=22
gender=男
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
package main
import (
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
)
type Student struct {
Name string `info:"name"`
Age int `info:"age"`
Sex string `info:"gender"`
}
func main () {
var s Student
file , err := os . ReadFile ( "./info.txt" )
if err != nil {
fmt . Println ( err )
}
//fmt.Println(string(file))
LoadingFile ( string ( file ), & s )
fmt . Printf ( "%#v" , s )
}
func LoadingFile ( s string , x interface {}) error {
tpy := reflect . TypeOf ( x )
val := reflect . ValueOf ( x )
// 第一步判断必须是指针传入
if tpy . Kind () != reflect . Ptr {
fmt . Println ( "except struct ptr" )
return errors . New ( "except struct ptr" )
}
// 第二部判断是否是结构体
if tpy . Elem (). Kind () != reflect . Struct {
fmt . Println ( "except struct " )
return errors . New ( "except struct" )
}
splitStr := strings . Split ( s , "\n" )
for _ , v := range splitStr {
kvList := strings . Split ( v , "=" )
if len ( kvList ) != 2 {
continue
}
fieldName := ""
key := strings . TrimSpace ( kvList [ 0 ])
value := strings . TrimSpace ( kvList [ 1 ])
// 遍历结构体字段
for i := 0 ; i < tpy . Elem (). NumField (); i ++ {
f := tpy . Elem (). Field ( i )
tagVal := f . Tag . Get ( "info" )
if tagVal == key {
fieldName = f . Name
break
}
}
if fieldName == "" {
continue
}
//根据找到的结构体字段名找到结构体字段
vField := val . Elem (). FieldByName ( fieldName )
switch vField . Type (). Kind () {
case reflect . String :
vField . SetString ( value )
case reflect . Int , reflect . Int8 , reflect . Int16 , reflect . Int32 , reflect . Int64 :
intVal , err := strconv . ParseInt ( value , 10 , 64 )
if err != nil {
return err
}
vField . SetInt ( intVal )
default :
return fmt . Errorf ( "unsupport value type:%v" , vField . Type (). Kind ())
}
}
return nil
}
refelct反射功能比较强大,但也不能滥用,因为代码可读性比较差。而且性能比较差,反射的类型错误在程序运行的时候才会panic。