反射是值在程序运行期间对程序本身进行访问和修改的能力. 程序在编译时, 变量被转换为内存地址, 程序运行时, 程序无法获取自身的信息.
支持反射的语言可以在程序编译期将变量的反射信息, 如字段名称, 类型信息, 结构体信息等整合到可执行文件中, 并给程序提供接口访问反射信息, 这样就可以在程序运行期获取类型的反射信息, 并且有能力修改它们.
任意接口值在反射中都可以理解为由 Type 和 Value 组成的. Go 语言的反射是由 reflect
包提供的, 它定义了两个重要的类型 reflect.Type
和 reflect.Value
, 并提供了 reflect.TypeOf
和 reflect.ValueOf
函数来获取任意对象的 Value 和 Type.
反射的类型接口 reflect.Type
在 Go 语言程序中, 使用 reflect.TypeOf()
函数可以获得任意值的 reflect.Type
接口对象, 程序通过类型对象可以访问对象的类型信息.
1 2 3 4 5 6 7 8 9 10 import ( "fmt" "reflect" ) func main () { var n int = 20 typeOfn := reflect.TypeOf(n) fmt.Println("type:" , typeOfn.Name(), "kind:" , typeOfn.Kind()) }
类型名称(Name)与种类(Kind)
Name(类型名称) 返回反射类型的的名称, 包括 Go 语言中原生数据类型及通过 type 关键字自定义的数据类型. 获取方式为 reflect.Type
的 Name()
方法
Kind(种类) 指反射类型所属的种类, 它仅包含 Go 语言中原生数据种类. 获取方式为 reflect.Type
的 Kind()
方法, 返回 reflect.Kind
类型的常量
在如下示例中, stu
对象的反射类型名称为自定义的 Student
, 而其所属的反射种类为 struct
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import ( "fmt" "reflect" ) type Student struct { Name string Age int } func main () { stu := Student{ Name: "tom" , Age: 20 , } typeOfstu := reflect.TypeOf(stu) fmt.Println("type:" , typeOfstu.Name(), "kind:" , typeOfstu.Kind()) }
特殊反射种类的类型名称 获取反射种类(Kind)为 Array
, Chan
, Map
, Ptr
或 Slice
的对象的反射类型名称时, 通过 Name()
方法获得的类型名称为空字符串(“”)
要想获取以上对象的反射类型名称, 需要通过 Elem()
方法获取反射类型的元素类型, 然后再通过 Name()
方法获取其反射类型的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import ( "fmt" "reflect" ) type Student struct { Name string Age int } func main () { stu := Student{ Name: "tom" , Age: 20 , } ptrOfstu := &stu typeOfptr := reflect.TypeOf(ptrOfstu) fmt.Println("type:" , typeOfptr.Name(), "kind:" , typeOfptr.Kind()) elem := typeOfptr.Elem() fmt.Println("elem's type:" , elem.Name(), "elem's kind:" , elem.Kind()) }
reflect.Type
的 Elem()
方法仅当反射类型的种类是 Array
, Chan
, Map
, Ptr
或 Slice
时才可以获取其元素类型, 否则 Elem()
方法会引发 panics.
使用反射获取结构体的成员及方法信息 通过 reflect.TypeOf()
获得反射对象信息后, 如果它所属种类是结构体,可通过 reflect.Type
的 NumField()
和 Field()
方法获得结构体成员的详细信息, 可通过NumMethod()
和 Method()
方法获取结构体的方法详细信息
reflect.Type
中与成员及方法获取相关的方法如下表所示
方法
参数
返回值
说明
NumField()
-
int
返回结构体成员字段数量
Field(i int)
字段索引,从 0 开始
reflect.StructField
根据索引返回索引对应的结构体字段的信息
NumMethod()
-
int
返回结构体方法数量
Method(i int)
字段索引,从 0 开始
reflect.Method
根据索引返回索引对应的结构体方法的信息, 方法的排序是根据 ascii 码的先后顺序进行排序的
需要注意的是, 方法相关信息可直接通过 reflect.TypeOf(&object).Method()
方法获取, 且通过这种方法获取的方法包括 receiver 为 &object
的方法. reflect.TypeOf(object).Method()
仅返回 receiver 为 object
的方法. 而字段相关信息只能通过 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 import ( "fmt" "reflect" ) type Student struct { Name string `json: "name"` Age int `json: "age"` } func (stu Student) GetSum (n1, n2 int ) int { return n1 + n2 } func (stu *Student) Print () { fmt.Println(*stu) } func (stu *Student) Set (name string , age int ) { stu.Name = name stu.Age = age } func main () { stu := Student{ Name: "tom" , Age: 20 , } typeOfstu := reflect.TypeOf(stu) kindOfstu := typeOfstu.Kind() typeOfptr := reflect.TypeOf(&stu) if kindOfstu != reflect.Struct { fmt.Println("except struct..." ) return } fieldNum := typeOfstu.NumField() fmt.Println("字段个数为: " , fieldNum) for i := 0 ; i < fieldNum; i++ { field := typeOfstu.Field(i) fmt.Println(field) fmt.Printf("字段名: %v, 字段类型: %v, 字段的 json 标签: %v\n" , field.Name, field.Type, field.Tag.Get("json" )) } methodNumOfstu := typeOfstu.NumMethod() fmt.Println("stu方法个数为: " , methodNumOfstu) for i := 0 ; i < methodNumOfstu; i++ { method := typeOfstu.Method(i) fmt.Println(method) fmt.Printf("方法名: %v, 方法类型: %v\n" , method.Name, method.Type) } methodNumOfptr := typeOfptr.NumMethod() fmt.Println("&stu方法个数为: " , methodNumOfptr) for i := 0 ; i < methodNumOfptr; i++ { method := typeOfptr.Method(i) fmt.Println(method) fmt.Printf("方法名: %v, 方法类型: %v, 方法对象%v\n" , method.Name, method.Type, method.Func) } }
反射的值对象 reflect.Value
使用 reflect.ValueOf()
函数可以获得对象的反射值对象 reflect.Value
. reflect.Value
对象可以通过调用 Type()
方法返回 reflect.Type
对象, 完成值到类型对象的转换.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import ( "fmt" "reflect" ) func main () { var n int = 20 valueOfN := reflect.ValueOf(n) typeOfN := valueOfN.Type() fmt.Printf("值为: %v, 实际类型为 %T" , valueOfN, valueOfN) n2, ok := valueOfN.Interface().(int ) if ok { fmt.Printf("转换后值为: %v, 类型为 %T" , n2, n2) } }
通过以上可以看得出, 实际类型对象将自身通过接口方式传入 reflect.ValueOf(i interface{})
方法, 返回 reflect.Value
对象. reflect.Value
对象通过调用自身 Interface()
方法, 返回接口类型, 该接口类型可通过类型断言转化为实际类型对象.
使用反射获取结构体的成员值及方法信息 通过 reflect.ValueOf()
获得反射对象值信息后, 如果它所属种类是结构体,可通过 reflect.Value
的 NumField()
和 Field()
方法获得结构体成员的值信息, 可通过NumMethod()
和 Method()
方法获取结构体的方法等信息
reflect.Value
中与成员及方法获取相关的方法如下表所示:
方法
参数
返回值
说明
NumField()
-
int
返回结构体成员字段数量
Field(i int)
字段索引,从 0 开始
reflect.Value
根据索引返回结构体字段对应的值对象
NumMethod()
-
int
返回结构体方法数量
Method(i int)
字段索引,从 0 开始
reflect.Value
根据索引返回结构体方法对应的值对象, 输出为内存地址(尚不清楚是什么地址), 方法的排序是根据 ascii 码的先后顺序进行排序的
需要注意的是, 方法相关信息可直接通过 reflect.ValueOf(&object).Method()
方法获取, 且通过这种方法获取的方法包括 receiver 为 &object
的方法. reflect.ValueOf(object).Method()
仅返回 receiver 为 object
的方法. 而字段值对象相关信息只能通过 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 import ( "fmt" "reflect" ) type Student struct { Name string `json: "name"` Age int `json: "age"` } func (stu Student) GetSum (n1, n2 int ) int { return n1 + n2 } func (stu *Student) Print () { fmt.Println(*stu) } func (stu *Student) Set (name string , age int ) { stu.Name = name stu.Age = age } func main () { stu := Student{ Name: "tom" , Age: 20 , } valueOfstu := reflect.ValueOf(stu) valueOfptr := reflect.ValueOf(&stu) typeOfStu := valueOfstu.Type() kindOfstu := valueOfstu.Kind() if kindOfstu != reflect.Struct { fmt.Println("except struct..." ) return } fmt.Println(typeOfStu) fieldNum := valueOfstu.NumField() fmt.Println("字段个数为: " , fieldNum) for i := 0 ; i < fieldNum; i++ { field := valueOfstu.Field(i) fmt.Println(field) } methodNumOfstu := valueOfstu.NumMethod() fmt.Println("stu方法个数为: " , methodNumOfstu) for i := 0 ; i < methodNumOfstu; i++ { method := valueOfstu.Method(i) fmt.Println(method) } methodNumOfptr := valueOfptr.NumMethod() fmt.Println("&stu方法个数为: " , methodNumOfptr) for i := 0 ; i < methodNumOfptr; i++ { method := valueOfptr.Method(i) fmt.Println(method) } }
使用反射调用结构体的方法 继续看上一个例子, 通过反射如何调用 Student
中的方法呢?
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 import ( "fmt" "reflect" ) type Student struct { Name string `json: "name"` Age int `json: "age"` } func (stu Student) GetSum (n1, n2 int ) int { return n1 + n2 } func (stu *Student) Print () { fmt.Println(*stu) } func (stu *Student) Set (name string , age int ) { stu.Name = name stu.Age = age } func main () { stu := Student{ Name: "tom" , Age: 20 , } valueOfStu := reflect.ValueOf(&stu) var paramsGetSum []reflect.Value paramsGetSum = append (paramsGetSum, reflect.ValueOf(10 )) paramsGetSum = append (paramsGetSum, reflect.ValueOf(20 )) resGetSum := valueOfStu.Method(0 ).Call(paramsGetSum) fmt.Printf("调用结果为 %v, 类型为 %T\n" , resGetSum, resGetSum) resGetSum0 := resGetSum[0 ] fmt.Printf("第一个返回值为 %v, 类型为 %T\n" , resGetSum0, resGetSum0) realRes0 := resGetSum[0 ].Interface().(int ) fmt.Printf("实际结果为 %v, 类型为 %T\n" , realRes0, realRes0) var paramsPrint []reflect.Value resPrint := valueOfStu.Method(1 ).Call(paramsPrint) fmt.Printf("调用结果为 %v, 类型为 %T" , resPrint, resPrint) var paramsSet []reflect.Value paramsSet = append (paramsSet, reflect.ValueOf("jack" )) paramsSet = append (paramsSet, reflect.ValueOf(30 )) resSet := valueOfStu.Method(2 ).Call(paramsSet) fmt.Printf("调用结果为 %v, 类型为 %T\n" , resSet, resSet) fmt.Println(stu) }